[
  {
    "path": ".dockerignore",
    "content": ".git/\n"
  },
  {
    "path": ".drone.yml",
    "content": "kind: pipeline\ntype: docker\nname: deployement-local\n\nvolumes:\n  - name: repo\n    host:\n      path: /media/raid5/data/packages/repos/apt/botamusique/\n\nsteps:\n  - name: build-web\n    image: node:16\n    commands:\n      - (cd web && npm install && npm run build)\n    when:\n      event:\n        - push\n        - tag\n\n  - name: translate-html\n    image: python:3\n    commands:\n      - pip3 install jinja2\n      - ./scripts/translate_templates.py --lang-dir lang/ --template-dir web/templates/\n    when:\n      event:\n        - push\n        - tag\n\n  - name: deploy-testing\n    image: debian\n    commands:\n      - apt-get -qq update && apt-get -qq install git > /dev/null\n      - sed -i 's/target_version = git/target_version = testing/' configuration.default.ini\n      - git fetch --tags\n      - version=$(git describe --tags)\n      - echo \"current git commit is $version\"\n      - echo $version > /mnt/botamusique/testing-version\n      - sed -i \"s/version = 'git'/version = '$version'/\" mumbleBot.py\n      - rm -rf .git*\n      - rm -rf web\n      - mkdir /tmp/botamusique\n      - cp -r . /tmp/botamusique/\n      - tar -czf /mnt/botamusique/sources-testing.tar.gz -C /tmp botamusique\n    volumes:\n      - name: repo\n        path: /mnt/botamusique/\n    when:\n      branch:\n        - master\n      event:\n        - push\n\n  - name: deploy-stable\n    image: debian\n    commands:\n      - apt-get -qq update && apt-get -qq install jq curl git pandoc python3-requests > /dev/null\n      - sed -i 's/target_version = git/target_version = stable/' configuration.default.ini\n      - git fetch --tags\n      - version=$(git describe --abbrev=0 --tags)\n      - echo \"version is $version\"\n      - echo $version > /mnt/botamusique/version\n      - sed -i \"s/version = 'git'/version = '$version'/\" mumbleBot.py\n      - curl --silent \"https://api.github.com/repos/azlux/botamusique/releases/latest\" | jq -r '.body' | pandoc --from gfm --to html - --output - > /mnt/botamusique/changelog\n      - rm -rf .git*\n      - rm -rf web\n      - mkdir /tmp/botamusique\n      - cp -r . /tmp/botamusique/\n      - tar -czf /mnt/botamusique/sources-stable.tar.gz -C /tmp botamusique\n    volumes:\n      - name: repo\n        path: /mnt/botamusique/\n    when:\n      event:\n        - tag\n\nnode:\n  location: local\n\ntrigger:\n  event:\n    exclude:\n      - cron\n\n---\nkind: pipeline\ntype: docker\nname: deployement-docker\n\nsteps:\n  - name: build-web\n    image: node:16\n    commands:\n      - (cd web && npm install && npm run build)\n    when:\n      event:\n        - push\n        - tag\n\n  - name: translate-html\n    image: python:3\n    commands:\n      - pip3 install jinja2\n      - ./scripts/translate_templates.py --lang-dir lang/ --template-dir web/templates/\n    when:\n      event:\n        - push\n        - tag\n        \n  - name: config-testing\n    image: debian\n    commands:\n      - sed -i 's/target_version = git/target_version = testing/' configuration.default.ini\n    when:\n      branch:\n        - master\n      event:\n        - push\n        \n  - name: docker-testing\n    image: thegeeklab/drone-docker-buildx\n    privileged: true\n    settings:\n      repo: azlux/botamusique\n      platforms: linux/amd64,linux/arm64,linux/arm/v7\n      username:\n        from_secret: docker_username\n      password:\n        from_secret: docker_password\n      tags: testing\n    when:\n      branch:\n        - master\n      event:\n        - push\n\n  - name: config-stable\n    image: debian\n    commands:\n      - sed -i 's/target_version = git/target_version = stable/' configuration.default.ini\n    when:\n      event:\n        - tag\n\n  - name: docker-stable\n    image: thegeeklab/drone-docker-buildx\n    privileged: true\n    settings:\n      repo: azlux/botamusique\n      platforms: linux/amd64,linux/arm64,linux/arm/v7\n      username:\n        from_secret: docker_username\n      password:\n        from_secret: docker_password\n      tags: latest\n    when:\n      event:\n        - tag\n\nnode:\n  location: external\n\ntrigger:\n  event:\n    exclude:\n      - cron\n\n---\nkind: pipeline\ntype: docker\nname: translation-traduora\n    \nsteps:\n  - name: fetch-translation\n    image: debian\n    environment:\n      TRADUORA_R_CLIENT:\n        from_secret: TRADUORA_R_CLIENT\n      TRADUORA_R_SECRET:\n        from_secret: TRADUORA_R_SECRET\n      GITHUB_API:\n        from_secret: GITHUB_API\n    commands:\n      - apt update && apt install -y git python3-requests hub\n      - PUSH=true SOURCE_DIR=$(pwd) ./scripts/commit_new_translation.sh\n\nnode:\n  location: external\n    \ntrigger:\n  event:\n    - cron\n  cron:\n    - auto-fetch-lang\n\n---\nkind: pipeline\ntype: docker\nname: translation-git\n    \nsteps:\n  - name: push-translation\n    image: debian\n    environment:\n      TRADUORA_R_CLIENT:\n        from_secret: TRADUORA_R_CLIENT\n      TRADUORA_R_SECRET:\n        from_secret: TRADUORA_R_SECRET\n      TRADUORA_W_CLIENT:\n        from_secret: TRADUORA_W_CLIENT\n      TRADUORA_W_SECRET:\n        from_secret: TRADUORA_W_SECRET\n      GITHUB_API:\n        from_secret: GITHUB_API\n    commands:\n      - apt update && apt install -y git python3-requests hub\n      - SOURCE_DIR=$(pwd) ./scripts/update_translation_to_server.sh\n    when:\n      branch:\n        - master\n      event:\n        - push\n\nnode:\n  location: external\n    \ntrigger:\n  event:\n    exclude:\n      - cron\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "open_collective: botamusique\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**Affected version**\nThe exact version you're using (git commit id). You should **always** only report bugs which you can reproduce on the latest version (`uif` branch), however **always** state the current commit id here (in case there are new commits between your report and us looking at it)\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n - OS: [e.g. iOS]\n - Browser [e.g. chrome, safari]\n - Version [e.g. 22]\n\n**Smartphone (please complete the following information):**\n - Device: [e.g. iPhone6]\n - OS: [e.g. iOS8.1]\n - Browser [e.g. stock browser, safari]\n - Version [e.g. 22]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".gitignore",
    "content": "# Created by .ignore support plugin (hsz.mobi)\n### Python template\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n\nconfiguration.ini\n.vscode/settings.json\n2019-07-27 22_09_08-radiobrowser.py - botamusique - Visual Studio Code.png\n.DS_Store\n*.pem\n\nmusic_folder/\ntmp/\n\n*.db\n\ntemplates/*.html\n\n# Pycharm\n.idea/\n"
  },
  {
    "path": ".gitmodules",
    "content": ""
  },
  {
    "path": "Dockerfile",
    "content": "ARG ARCH=\nFROM python:3.11-slim-bullseye AS python-builder\nENV DEBIAN_FRONTEND=noninteractive\nWORKDIR /botamusique\n\nRUN apt-get update \\\n    && apt-get install --no-install-recommends -y gcc g++ ffmpeg libjpeg-dev libmagic-dev opus-tools zlib1g-dev \\\n    && rm -rf /var/lib/apt/lists/*\nCOPY . /botamusique\nRUN python3 -m venv venv \\\n    && venv/bin/pip install wheel \\\n    && venv/bin/pip install -r requirements.txt\n\nFROM python:3.11-slim-bullseye\nENV DEBIAN_FRONTEND noninteractive\nEXPOSE 8181\nRUN apt update && \\\n    apt install --no-install-recommends -y opus-tools ffmpeg libmagic-dev curl tar && \\\n    rm -rf /var/lib/apt/lists/*\nCOPY --from=python-builder /botamusique /botamusique\nWORKDIR /botamusique\nRUN chmod +x entrypoint.sh\n\nENTRYPOINT [ \"/botamusique/entrypoint.sh\" ]\nCMD [\"venv/bin/python\", \"mumbleBot.py\"]\n"
  },
  {
    "path": "Dockerfile.local",
    "content": "ARG ARCH=\n\nFROM ${ARCH}python:3-slim-bullseye AS source\nARG VERSION=master\nENV DEBIAN_FRONTEND=noninteractive\nWORKDIR /botamusique\nRUN apt-get update && apt-get install -y git \nRUN git clone --recurse-submodules https://github.com/azlux/botamusique.git . && git checkout $VERSION\n\n\nFROM ${ARCH}python:3-slim-bullseye AS python-builder\nENV DEBIAN_FRONTEND=noninteractive\nWORKDIR /botamusique\nRUN apt-get update \\\n    && apt-get install -y gcc ffmpeg libjpeg-dev libmagic-dev opus-tools zlib1g-dev \\\n    && rm -rf /var/lib/apt/lists/*\nCOPY --from=source /botamusique .\nRUN python3 -m venv venv \\\n    && venv/bin/pip install wheel \\\n    && venv/bin/pip install -r requirements.txt\n\n\nFROM ${ARCH}node:14-bullseye-slim AS node-builder\nENV DEBIAN_FRONTEND=noninteractive\nWORKDIR /botamusique/web\nCOPY --from=source /botamusique/web .\nRUN npm install\nRUN npm run build\n\n\nFROM ${ARCH}python:3-slim-bullseye AS template-builder\nENV DEBIAN_FRONTEND=noninteractive\nWORKDIR /botamusique\nCOPY --from=python-builder /botamusique .\nCOPY --from=node-builder /botamusique/templates templates\nRUN venv/bin/python scripts/translate_templates.py --lang-dir /botamusique/lang --template-dir /botamusique/web/templates\n\n\nFROM ${ARCH}python:3-slim-bullseye\nENV DEBIAN_FRONTEND=noninteractive\nEXPOSE 8181\nWORKDIR /botamusique\nRUN apt-get update \\\n    && apt-get install -y ffmpeg libmagic-dev opus-tools zlib1g \\\n    && rm -rf /var/lib/apt/lists/*\nCOPY --from=python-builder /botamusique .\nCOPY --from=node-builder /botamusique/static static\nCOPY --from=template-builder /botamusique/templates templates\nRUN chmod +x entrypoint.sh\nENTRYPOINT [ \"/botamusique/entrypoint.sh\" ]\nCMD [\"/botamusique/venv/bin/python\", \"/botamusique/mumbleBot.py\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2016 \n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Important Announcement\n\nHello everyone,\n\nFirst, let's look at the problems:\n1. I don't use mumble anymore, working on a bot you don't use produces a leak of testing and motivation.\n2. I don't code like before, my hobbies have changed, I maintain stuff I still use, but no real coding anymore.\n3. Botamusique is monolitique\n\nI've been trying to make a POC to change the monolitique part, to have a fully modulable bot, with asyncio and and feature/backend as plugins. But asyncio was blocking for me, especially to make the bot with fastapi, discord api / pymumble. It's 2 async loop and I don't have the knowledge to make it work.\nTo be transparent, botamusique was the biggest project I've done, one of the funniest. Thanks @TerryGeng for joining the adventure.\n\nI don't think I will be looking for a maintainer, the monolithic part of this project is not something that needs to be maintained.\n\n**This projet will be archived.**\n\nBUT If someone want to rewrite a bot, I'm ready to help with the projet : what to do, Errors to avoid, Design/architecture help (but no code). I think **_8 years_** on this projet (have start with [this small projet](https://github.com/azlux/MumbleRadioPlayer/commit/56ca276c5519fcb0e1af043beb043202e65c2cca)) can help someone.\n\nIt was really funny, thank all, for your support !\n\nSee you in space cowboy.\n\n-- Azlux\n\n-----\n\n<div align=\"center\">\n<img src=\"static/image/logo.png\" alt=\"botamusique\" width=\"200px\" />\n<h1>botamusique</h1>\n</div>\n\nBotamusique is a [Mumble](https://www.mumble.info/) music bot.\nPredicted functionalities will be those people would expect from any classic music player.\n\n[![Build Status](https://ci.azlux.fr/api/badges/azlux/botamusique/status.svg)](https://ci.azlux.fr/azlux/botamusique)\n\n## Features\n\n1. **Support multiple music sources:**\n    - Music files in local folders (which can be uploaded through the web interface).\n    - Youtube/Soundcloud URLs and playlists (everything supported by youtube-dl).\n    - Radio stations from URL and http://www.radio-browser.info API.\n2. **Modern and powerful web remote control interface.** Powered by Flask. Which supports:\n    - Playlist management.\n    - Music library management, including uploading, browsing all files and edit tags, etc.\n3. **Powerful command system.** Commands and words the bot says are fully customizable. Support partial-match for commands.\n4. **Ducking.** The bot would automatically lower its volume if people are talking.\n5. **Stereo sound.** After Mumble 1.4.0, stereo output support has been added. Our bot is designed to work nicely with it naturally.\n6. **Multilingual support.** A list of supported languages can be found below.\n\n\n## Screenshots\n\n![botamusique in Mumble channel](https://user-images.githubusercontent.com/2306637/75210917-68fbf680-57bd-11ea-9cf8-c0871edff13f.jpg)\n\n![botamusique web interface](https://user-images.githubusercontent.com/2306637/77822763-b4911f80-7130-11ea-9bc5-83c36c995ab9.png)\n\n-----\n## Quick Start Guide\n1. [Installation](#installation)\n1. [Configuration](#configuration)\n1. [Run the bot](#run-the-bot)\n1. [Operate the bot](#operate-the-bot)\n1. [Update](#update)\n1. [Known issues](#known-issues)\n1. [Contributors](#contributors)\n\n## Installation\n\n### Dependencies\n1. Install python. We require a python version of 3.6 or higher.\n1. Install [Opus Codec](https://www.opus-codec.org/) (which should be already installed if you installed Mumble or Murmur, or you may try to install `opus-tools` with your package manager).\n1. Install ffmpeg. If ffmpeg isn't in your package manager, you may need to find another source. I personally use [this repository](http://repozytorium.mati75.eu/) on my raspberry.\n\n\n### Docker\nSee https://github.com/azlux/botamusique/wiki/Docker-install\n\nBoth stable and nightly (developing) builds are available!\n\n### Manual install\n\n**Stable release (recommended)**\n\nThis is current stable version, with auto-update support. To install the stable release, run these lines in your terminal:\n```\ncurl -Lo botamusique.tar.gz http://packages.azlux.fr/botamusique/sources-stable.tar.gz\ntar -xzf botamusique.tar.gz\ncd botamusique\npython3 -m venv venv\nvenv/bin/pip install wheel\nvenv/bin/pip install -r requirements.txt\n```\n\n**Nightly build (developing version)**\n<details>\n  <summary>Click to expand!</summary>\n\nThis build reflects any newest change in the master branch, with auto-update support baked in. This version follow all commits into the master branch.\n```\ncurl -Lo botamusique.tar.gz http://packages.azlux.fr/botamusique/sources-testing.tar.gz\ntar -xzf botamusique.tar.gz\ncd botamusique\npython3 -m venv venv\nvenv/bin/pip install wheel\nvenv/bin/pip install -r requirements.txt\n```\n</details>\n\n**Build from source code**\n<details>\n  <summary>Click to expand!</summary>\n\nYou can checkout the master branch of our repo and compile everything by yourself.\nWe will test new features in the master branch, maybe sometimes post some hotfixes.\nPlease be noted that the builtin auto-update support doesn't track this version.\nIf you have no idea what these descriptions mean to you, we recommend you install the stable version above.\n```\ngit clone https://github.com/azlux/botamusique.git\ncd botamusique\npython3 -m venv venv\nvenv/bin/pip install wheel\nvenv/bin/pip install -r requirements.txt\n(cd web && npm install && npm run build)\nvenv/bin/python3 ./scripts/translate_templates.py --lang-dir lang/ --template-dir web/templates/\n```\n</details>\n\n## Configuration\nPlease copy `configuration.example.ini` into `configuration.ini`, follow the instructions in that file and uncomment options you would like to modify. Not all sections are needed. You may just keep the options that matter to you. For example, if you only would like to set `host`, all you need you is keep \n```\n[server]\nhost=xxxxxx\n```\nin your `configuration.ini`.\n\nPlease DO NOT MODIFY `configuration.default.ini`, since if the bot realizes one option is undefined in `configuration.ini`, it will look into `configuration.default.ini` for the default value of that option. This file will be constantly overridden in each update.\n\nWe list some basic settings for you to quickly get things working.\n\n### Basic settings\n1. Usually, the first thing is to set the Murmur server you'd like the bot to connect to. You may also specify which channel the bot stays, and tokens used by the bot.\n```\n[server]\nhost = 127.0.0.1\nport = 64738\n```\n\n2. You need to specify a folder that stores your music files. The bot will look for music and upload files into that folder. You also need to specify a temporary folder to store music file downloads from URLs.\n```\n[bot]\nmusic_folder = music_folder/\ntmp_folder = /tmp/\n```\n\n3. **Web interface is disabled by default** for performance and security reasons. It is extremely powerful, so we encourage you to have a try. To enable it, set\n```\n[webinterface]\nenabled = True\n```\n\nDefault binding address is\n```\nlistening_addr = 127.0.0.1\nlistening_port = 8181\n```\n\nYou can access the web interface through http://127.0.0.1:8181 if you keep it unchanged.\n\nNote: Listening to address `127.0.0.1` will only accept requests from localhost. _If you would like to connect from the public internet, you need to set it to `0.0.0.0`, and set up username and password to impose access control._ In addition, if the bot is behind a router, you should also properly set forwarding rules in you NAT configuration to forward requests to the bot.\n\n4. The default language is English, but you can change it in `[bot]` section:\n```\n[bot]\nlanguage=en_US\n```\n\nAvailable translations can be found inside `lang/` folder. Currently, options are\n\n - `en_US`, English\n - `es_ES`, Spanish\n - `fr_FR`, French\n - `it_IT`, Italian\n - `ja_JP`, Japanese\n - `zh_CN`, Chinese\n\n5. Generate a certificate (Optional, but recommended)\n\nBy default, murmur server uses certificates to identify users. Without a valid certificate, you wouldn't able to register the bot into your Murmur server. Some server even refused users without a certificate. Therefore, it is recommended to generate a certificate for the bot. If you have a certificate (for say, `botmusique.pem` in the folder of the bot), you can specify its location in\n```\n[server]\ncertificate=botamusique.pem\n```\n\nIf you don't have a certificate, you may generate one by:\n`openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout botamusique.pem -out botamusique.pem -subj \"/CN=botamusique\"`\n\n\n### Sections explained\n- `server`: configuration about the server. Will be overridden by the `./mumbleBot.py` parameters.\n- `bot`: basic configuration of the bot, eg. name, comment, folder, default volume, etc.\n- `webinterface`: basic configuration about the web interface.\n- `commands`: you can customize the command you want for each action (eg. put `help = helpme` , the bot will respond to `!helpme`)\n- `radio`: a list of default radio (eg. play a jazz radio with the command `!radio jazz`)\n- `debug`: option to activate ffmpeg or pymumble debug output.\n\n\n## Run the bot\nIf you have set up everything in your `configuration.ini`, you can\n`venv/bin/python mumbleBot.py --config configuration.ini`\n\nOr you can\n`venv/bin/python mumbleBot.py -s HOST -u BOTNAME -P PASSWORD -p PORT -c CHANNEL -C /path/to/botamusique.pem`\n\nIf you want information about auto-starting and auto-restarting of the bot, you can check out the wiki page [Run botamusique as a daemon In the background](https://github.com/azlux/botamusique/wiki/Run-botamusique-as-a-daemon-In-the-background).\n\n**For the detailed manual of using botamusique, please see the [wiki](https://github.com/azlux/botamusique/wiki).**\n\n## Operate the bot\n\nYou can control the bot by both commands sent by text message and the web interface.\n\nBy default, all commands start with `!`. You can type `!help` in the text message to see the full list of commands supported, or see the examples on the [wiki page](https://github.com/azlux/botamusique/wiki/Command-Help-and-Examples).\n\nThe web interface can be used if you'd like an intuitive way of interacting with the bot. Through it is fairly straightforward, a walk-through can be found on the [wiki page](https://github.com/azlux/botamusique/wiki/Web-interface-walk-through).\n\n## Update\n\nIf you enable `auto_check_update`, the bot will check for updates every time it starts.\nIf you are using the recommended install, you can send `!update` to the bot (command by default).\n\nIf you are using git, you need to update manually:\n```\ngit pull --all\ngit submodule update\nvenv/bin/pip install --upgrade -r requirements.txt\n```\n\n\n## Known issues\n\n1. During installation, you may encounter the following error:\n```\nImportError: libtiff.so.5: cannot open shared object file: No such file or directory\n```\nYou need to install a missing library: `apt install libtiff5`\n\n2. In the beginning, you may encounter the following error even if you have installed all requirements:\n```\nException: Could not find opus library. Make sure it is installed.\n```\nYou need to install the opus codec (not embedded in all system): `apt install libopus0`\n\n3. MacOS Users may encounter the following error:\n```\nImportError: failed to find libmagic.  Check your installation\n```\nThis is caused by missing `libmagic` binaries and can be solved by\n```bash\nbrew install libmagic\n\n```\nOne may also install `python-magic-bin` instead of `python-magic`.\n\n5. If you have a large amount of music files (>1000), it may take some time for the bot to boot, since\nit will build up the cache for the music library on booting. You may want to disable this auto-scanning by\nsetting ``refresh_cache_on_startup=False`` in `[bot]` section and control the scanning manually by\n``!rescan`` command and the *Rescan Files* button on the web interface.\n\n6. Alpine Linux requires some extra dependencies during the installation (in order to compile Pillow):\n```\npython3-dev musl-lib libmagic jpeg-dev zlib-dev gcc\n```\nFor more information, see [#122](https://github.com/azlux/botamusique/issues/122).\n\n## _I need help!_\n\nIf you ran into some problems in using the bot, or discovered bugs and want to talk to us, you may\n\n - Start a new issue,\n - Ask in the Matrix channel of Mumble [#mumble:matrix.org](https://matrix.to/#/#mumble:matrix.org) (we are usually there to help).\n\n## Contributors\nIf you want to help us develop, you're welcome to fork and submit pull requests (fixes and new features).\nWe are looking for people helping us translating the bot. If you'd like to add a new language or fix errors in existed translations,\nfeel free to catch us in the IRC channel #mumble, or just email us!\n\nThe following people joined as collaborators for a faster development, big thanks to them:\n- @TerryGeng\n- @mertkutay\n\nFeel free to ask me if you want to help actively without using pull requests.\n"
  },
  {
    "path": "command.py",
    "content": "# coding=utf-8\nimport logging\nimport secrets\nimport datetime\nimport json\nimport re\nimport pymumble_py3 as pymumble\n\nfrom constants import tr_cli as tr\nfrom constants import commands\nimport interface\nimport util\nimport variables as var\nfrom pyradios import RadioBrowser\nfrom database import SettingsDatabase, MusicDatabase, Condition\nimport media.playlist\nfrom media.item import item_id_generators, dict_to_item, dicts_to_items, ValidationFailedError\nfrom media.cache import get_cached_wrapper_from_scrap, get_cached_wrapper_by_id, get_cached_wrappers_by_tags, \\\n    get_cached_wrapper, get_cached_wrappers, get_cached_wrapper_from_dict, get_cached_wrappers_from_dicts\nfrom media.url_from_playlist import get_playlist_info\n\nlog = logging.getLogger(\"bot\")\n\n\ndef register_all_commands(bot):\n    bot.register_command(commands('add_from_shortlist'), cmd_shortlist)\n    bot.register_command(commands('add_tag'), cmd_add_tag)\n    bot.register_command(commands('change_user_password'), cmd_user_password, no_partial_match=True)\n    bot.register_command(commands('clear'), cmd_clear)\n    bot.register_command(commands('current_music'), cmd_current_music)\n    bot.register_command(commands('delete_from_library'), cmd_delete_from_library)\n    bot.register_command(commands('ducking'), cmd_ducking)\n    bot.register_command(commands('ducking_threshold'), cmd_ducking_threshold)\n    bot.register_command(commands('ducking_volume'), cmd_ducking_volume)\n    bot.register_command(commands('find_tagged'), cmd_find_tagged)\n    bot.register_command(commands('help'), cmd_help, no_partial_match=False, access_outside_channel=True)\n    bot.register_command(commands('joinme'), cmd_joinme, access_outside_channel=True)\n    bot.register_command(commands('last'), cmd_last)\n    bot.register_command(commands('list_file'), cmd_list_file)\n    bot.register_command(commands('mode'), cmd_mode)\n    bot.register_command(commands('pause'), cmd_pause)\n    bot.register_command(commands('play'), cmd_play)\n    bot.register_command(commands('play_file'), cmd_play_file)\n    bot.register_command(commands('play_file_match'), cmd_play_file_match)\n    bot.register_command(commands('play_playlist'), cmd_play_playlist)\n    bot.register_command(commands('play_radio'), cmd_play_radio)\n    bot.register_command(commands('play_tag'), cmd_play_tags)\n    bot.register_command(commands('play_url'), cmd_play_url)\n    bot.register_command(commands('queue'), cmd_queue)\n    bot.register_command(commands('random'), cmd_random)\n    bot.register_command(commands('rb_play'), cmd_rb_play)\n    bot.register_command(commands('rb_query'), cmd_rb_query)\n    bot.register_command(commands('remove'), cmd_remove)\n    bot.register_command(commands('remove_tag'), cmd_remove_tag)\n    bot.register_command(commands('repeat'), cmd_repeat)\n    bot.register_command(commands('requests_webinterface_access'), cmd_web_access)\n    bot.register_command(commands('rescan'), cmd_refresh_cache, no_partial_match=True)\n    bot.register_command(commands('search'), cmd_search_library)\n    bot.register_command(commands('skip'), cmd_skip)\n    bot.register_command(commands('stop'), cmd_stop)\n    bot.register_command(commands('stop_and_getout'), cmd_stop_and_getout)\n    bot.register_command(commands('version'), cmd_version, no_partial_match=True)\n    bot.register_command(commands('volume'), cmd_volume)\n    bot.register_command(commands('yt_play'), cmd_yt_play)\n    bot.register_command(commands('yt_search'), cmd_yt_search)\n\n    # admin command\n    bot.register_command(commands('add_webinterface_user'), cmd_web_user_add, admin=True)\n    bot.register_command(commands('drop_database'), cmd_drop_database, no_partial_match=True, admin=True)\n    bot.register_command(commands('kill'), cmd_kill, admin=True)\n    bot.register_command(commands('list_webinterface_user'), cmd_web_user_list, admin=True)\n    bot.register_command(commands('remove_webinterface_user'), cmd_web_user_remove, admin=True)\n    bot.register_command(commands('max_volume'), cmd_max_volume, admin=True)\n    bot.register_command(commands('update'), cmd_update, no_partial_match=True, admin=True)\n    bot.register_command(commands('url_ban'), cmd_url_ban, no_partial_match=True, admin=True)\n    bot.register_command(commands('url_ban_list'), cmd_url_ban_list, no_partial_match=True, admin=True)\n    bot.register_command(commands('url_unban'), cmd_url_unban, no_partial_match=True, admin=True)\n    bot.register_command(commands('url_unwhitelist'), cmd_url_unwhitelist, no_partial_match=True, admin=True)\n    bot.register_command(commands('url_whitelist'), cmd_url_whitelist, no_partial_match=True, admin=True)\n    bot.register_command(commands('url_whitelist_list'), cmd_url_whitelist_list, no_partial_match=True, admin=True)\n    bot.register_command(commands('user_ban'), cmd_user_ban, no_partial_match=True, admin=True)\n    bot.register_command(commands('user_unban'), cmd_user_unban, no_partial_match=True, admin=True)\n\n    # Just for debug use\n    bot.register_command('rtrms', cmd_real_time_rms, True)\n    # bot.register_command('loop', cmd_loop_state, True)\n    # bot.register_command('item', cmd_item, True)\n\n\ndef send_multi_lines(bot, lines, text, linebreak=\"<br />\"):\n    global log\n\n    msg = \"\"\n    br = \"\"\n    for newline in lines:\n        msg += br\n        br = linebreak\n        if bot.mumble.get_max_message_length() \\\n                and (len(msg) + len(newline)) > (bot.mumble.get_max_message_length() - 4):  # 4 == len(\"<br>\")\n            bot.send_msg(msg, text)\n            msg = \"\"\n        msg += newline\n\n    bot.send_msg(msg, text)\n\n\ndef send_multi_lines_in_channel(bot, lines, linebreak=\"<br />\"):\n    global log\n\n    msg = \"\"\n    br = \"\"\n    for newline in lines:\n        msg += br\n        br = linebreak\n        if bot.mumble.get_max_message_length() \\\n                and (len(msg) + len(newline)) > (bot.mumble.get_max_message_length() - 4):  # 4 == len(\"<br>\")\n            bot.send_channel_msg(msg)\n            msg = \"\"\n        msg += newline\n\n    bot.send_channel_msg(msg)\n\n\ndef send_item_added_message(bot, wrapper, index, text):\n    if index == var.playlist.current_index + 1:\n        bot.send_msg(tr('file_added', item=wrapper.format_song_string()) +\n                     tr('position_in_the_queue', position=tr('next_to_play')), text)\n    elif index == len(var.playlist) - 1:\n        bot.send_msg(tr('file_added', item=wrapper.format_song_string()) +\n                     tr('position_in_the_queue', position=tr('last_song_on_the_queue')), text)\n    else:\n        bot.send_msg(tr('file_added', item=wrapper.format_song_string()) +\n                     tr('position_in_the_queue', position=f\"{index + 1}/{len(var.playlist)}.\"), text)\n\n\n# ---------------- Variables -----------------\n\nITEMS_PER_PAGE = 50\n\nsong_shortlist = []\n\n\n# ---------------- Commands ------------------\n\ndef cmd_joinme(bot, user, text, command, parameter):\n    global log\n\n    bot.mumble.users.myself.move_in(\n        bot.mumble.users[text.actor]['channel_id'], token=parameter)\n\n\ndef cmd_user_ban(bot, user, text, command, parameter):\n    global log\n\n    if parameter:\n        var.db.set(\"user_ban\", parameter, None)\n        bot.send_msg(tr(\"user_ban_success\", user=parameter), text)\n    else:\n        ban_list = \"<ul>\"\n        for i in var.db.items(\"url_ban\"):\n            ban_list += \"<li>\" + i[0] + \"</li>\"\n        ban_list += \"</ul>\"\n        bot.send_msg(tr(\"user_ban_list\", list=ban_list), text)\n\n\ndef cmd_user_unban(bot, user, text, command, parameter):\n    global log\n\n    if parameter and var.db.has_option(\"user_ban\", parameter):\n        var.db.remove_option(\"user_ban\", parameter)\n        bot.send_msg(tr(\"user_unban_success\", user=parameter), text)\n\n\ndef cmd_url_ban(bot, user, text, command, parameter):\n    global log\n\n    url = util.get_url_from_input(parameter)\n    if url:\n        _id = item_id_generators['url'](url=url)\n        var.cache.free_and_delete(_id)\n        var.playlist.remove_by_id(_id)\n    else:\n        if var.playlist.current_item() and var.playlist.current_item().type == 'url':\n            item = var.playlist.current_item().item()\n            url = item.url\n            var.cache.free_and_delete(item.id)\n            var.playlist.remove_by_id(item.id)\n        else:\n            bot.send_msg(tr('bad_parameter', command=command), text)\n            return\n\n    # Remove from the whitelist first\n    if var.db.has_option('url_whitelist', url):\n        var.db.remove_option(\"url_whitelist\", url)\n        bot.send_msg(tr(\"url_unwhitelist_success\", url=url), text)\n\n    if not var.db.has_option('url_ban', url):\n        var.db.set(\"url_ban\", url, None)\n    bot.send_msg(tr(\"url_ban_success\", url=url), text)\n\n\ndef cmd_url_ban_list(bot, user, text, command, parameter):\n    ban_list = \"<ul>\"\n    for i in var.db.items(\"url_ban\"):\n        ban_list += \"<li>\" + i[0] + \"</li>\"\n    ban_list += \"</ul>\"\n\n    bot.send_msg(tr(\"url_ban_list\", list=ban_list), text)\n\n\ndef cmd_url_unban(bot, user, text, command, parameter):\n    url = util.get_url_from_input(parameter)\n    if url:\n        var.db.remove_option(\"url_ban\", url)\n        bot.send_msg(tr(\"url_unban_success\", url=url), text)\n    else:\n        bot.send_msg(tr('bad_parameter', command=command), text)\n\n\ndef cmd_url_whitelist(bot, user, text, command, parameter):\n    url = util.get_url_from_input(parameter)\n    if url:\n        # Unban first\n        if var.db.has_option('url_ban', url):\n            var.db.remove_option(\"url_ban\", url)\n            bot.send_msg(tr(\"url_unban_success\"), text)\n\n        # Then add to whitelist\n        if not var.db.has_option('url_whitelist', url):\n            var.db.set(\"url_whitelist\", url, None)\n        bot.send_msg(tr(\"url_whitelist_success\", url=url), text)\n    else:\n        bot.send_msg(tr('bad_parameter', command=command), text)\n\n\ndef cmd_url_whitelist_list(bot, user, text, command, parameter):\n    ban_list = \"<ul>\"\n    for i in var.db.items(\"url_whitelist\"):\n        ban_list += \"<li>\" + i[0] + \"</li>\"\n    ban_list += \"</ul>\"\n\n    bot.send_msg(tr(\"url_whitelist_list\", list=ban_list), text)\n\n\ndef cmd_url_unwhitelist(bot, user, text, command, parameter):\n    url = util.get_url_from_input(parameter)\n    if url:\n        var.db.remove_option(\"url_whitelist\", url)\n        bot.send_msg(tr(\"url_unwhitelist_success\"), text)\n    else:\n        bot.send_msg(tr('bad_parameter', command=command), text)\n\n\ndef cmd_play(bot, user, text, command, parameter):\n    global log\n\n    params = parameter.split()\n    index = -1\n    start_at = 0\n    if len(params) > 0:\n        if params[0].isdigit() and 1 <= int(params[0]) <= len(var.playlist):\n            index = int(params[0])\n        else:\n            bot.send_msg(tr('invalid_index', index=parameter), text)\n            return\n\n        if len(params) > 1:\n            try:\n                start_at = util.parse_time(params[1])\n            except ValueError:\n                bot.send_msg(tr('bad_parameter', command=command), text)\n                return\n\n    if len(var.playlist) > 0:\n        if index != -1:\n            bot.play(int(index) - 1, start_at)\n\n        elif bot.is_pause:\n            bot.resume()\n        else:\n            bot.send_msg(var.playlist.current_item().format_current_playing(), text)\n    else:\n        bot.is_pause = False\n        bot.send_msg(tr('queue_empty'), text)\n\n\ndef cmd_pause(bot, user, text, command, parameter):\n    global log\n\n    bot.pause()\n    bot.send_channel_msg(tr('paused'))\n\n\ndef cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=False):\n    global log, song_shortlist\n\n    # assume parameter is a path\n    music_wrappers = get_cached_wrappers_from_dicts(var.music_db.query_music(Condition().and_equal('path', parameter)), user)\n    if music_wrappers:\n        var.playlist.append(music_wrappers[0])\n        log.info(\"cmd: add to playlist: \" + music_wrappers[0].format_debug_string())\n        send_item_added_message(bot, music_wrappers[0], len(var.playlist) - 1, text)\n        return\n\n    # assume parameter is a folder\n    music_wrappers = get_cached_wrappers_from_dicts(var.music_db.query_music(Condition()\n                                                                             .and_equal('type', 'file')\n                                                                             .and_like('path', parameter + '%')), user)\n    if music_wrappers:\n        msgs = [tr('multiple_file_added')]\n\n        for music_wrapper in music_wrappers:\n            log.info(\"cmd: add to playlist: \" + music_wrapper.format_debug_string())\n            msgs.append(\"<b>{:s}</b> ({:s})\".format(music_wrapper.item().title, music_wrapper.item().path))\n\n        var.playlist.extend(music_wrappers)\n\n        send_multi_lines_in_channel(bot, msgs)\n        return\n\n    # try to do a partial match\n    matches = var.music_db.query_music(Condition()\n                                       .and_equal('type', 'file')\n                                       .and_like('path', '%' + parameter + '%', case_sensitive=False))\n    if len(matches) == 1:\n        music_wrapper = get_cached_wrapper_from_dict(matches[0], user)\n        var.playlist.append(music_wrapper)\n        log.info(\"cmd: add to playlist: \" + music_wrapper.format_debug_string())\n        send_item_added_message(bot, music_wrapper, len(var.playlist) - 1, text)\n        return\n    elif len(matches) > 1:\n        song_shortlist = matches\n        msgs = [tr('multiple_matches')]\n        for index, match in enumerate(matches):\n            msgs.append(\"<b>{:d}</b> - <b>{:s}</b> ({:s})\".format(\n                index + 1, match['title'], match['path']))\n        msgs.append(tr(\"shortlist_instruction\"))\n        send_multi_lines(bot, msgs, text)\n        return\n\n    if do_not_refresh_cache:\n        bot.send_msg(tr(\"no_file\"), text)\n    else:\n        var.cache.build_dir_cache()\n        cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=True)\n\n\ndef cmd_play_file_match(bot, user, text, command, parameter, do_not_refresh_cache=False):\n    global log\n\n    if parameter:\n        file_dicts = var.music_db.query_music(Condition().and_equal('type', 'file'))\n        msgs = [tr('multiple_file_added') + \"<ul>\"]\n        try:\n            count = 0\n            music_wrappers = []\n            for file_dict in file_dicts:\n                file = file_dict['title']\n                match = re.search(parameter, file)\n                if match and match[0]:\n                    count += 1\n                    music_wrapper = get_cached_wrapper(dict_to_item(file_dict), user)\n                    music_wrappers.append(music_wrapper)\n                    log.info(\"cmd: add to playlist: \" + music_wrapper.format_debug_string())\n                    msgs.append(\"<li><b>{}</b> ({})</li>\".format(music_wrapper.item().title,\n                                                                 file[:match.span()[0]]\n                                                                 + \"<b style='color:pink'>\"\n                                                                 + file[match.span()[0]: match.span()[1]]\n                                                                 + \"</b>\"\n                                                                 + file[match.span()[1]:]\n                                                                 ))\n\n            if count != 0:\n                msgs.append(\"</ul>\")\n                var.playlist.extend(music_wrappers)\n                send_multi_lines_in_channel(bot, msgs, \"\")\n            else:\n                if do_not_refresh_cache:\n                    bot.send_msg(tr(\"no_file\"), text)\n                else:\n                    var.cache.build_dir_cache()\n                    cmd_play_file_match(bot, user, text, command, parameter, do_not_refresh_cache=True)\n\n        except re.error as e:\n            msg = tr('wrong_pattern', error=str(e))\n            bot.send_msg(msg, text)\n    else:\n        bot.send_msg(tr('bad_parameter', command=command), text)\n\n\ndef cmd_play_url(bot, user, text, command, parameter):\n    global log\n\n    url = util.get_url_from_input(parameter)\n    if url:\n        music_wrapper = get_cached_wrapper_from_scrap(type='url', url=url, user=user)\n        var.playlist.append(music_wrapper)\n\n        log.info(\"cmd: add to playlist: \" + music_wrapper.format_debug_string())\n        send_item_added_message(bot, music_wrapper, len(var.playlist) - 1, text)\n\n        if len(var.playlist) == 2:\n            # If I am the second item on the playlist. (I am the next one!)\n            bot.async_download_next()\n    else:\n        bot.send_msg(tr('bad_parameter', command=command), text)\n\n\ndef cmd_play_playlist(bot, user, text, command, parameter):\n    global log\n\n    offset = 0  # if you want to start the playlist at a specific index\n    try:\n        offset = int(parameter.split(\" \")[-1])\n    except ValueError:\n        pass\n\n    url = util.get_url_from_input(parameter)\n    if url:\n        log.debug(f\"cmd: fetching media info from playlist url {url}\")\n        items = get_playlist_info(url=url, start_index=offset, user=user)\n        if len(items) > 0:\n            items = var.playlist.extend(list(map(lambda item: get_cached_wrapper_from_scrap(**item), items)))\n            for music in items:\n                log.info(\"cmd: add to playlist: \" + music.format_debug_string())\n        else:\n            bot.send_msg(tr(\"playlist_fetching_failed\"), text)\n    else:\n        bot.send_msg(tr('bad_parameter', command=command), text)\n\n\ndef cmd_play_radio(bot, user, text, command, parameter):\n    global log\n\n    if not parameter:\n        all_radio = var.config.items('radio')\n        msg = tr('preconfigurated_radio')\n        for i in all_radio:\n            comment = \"\"\n            if len(i[1].split(maxsplit=1)) == 2:\n                comment = \" - \" + i[1].split(maxsplit=1)[1]\n            msg += \"<br />\" + i[0] + comment\n        bot.send_msg(msg, text)\n    else:\n        if var.config.has_option('radio', parameter):\n            parameter = var.config.get('radio', parameter)\n            parameter = parameter.split()[0]\n        url = util.get_url_from_input(parameter)\n        if url:\n            music_wrapper = get_cached_wrapper_from_scrap(type='radio', url=url, user=user)\n\n            var.playlist.append(music_wrapper)\n            log.info(\"cmd: add to playlist: \" + music_wrapper.format_debug_string())\n            send_item_added_message(bot, music_wrapper, len(var.playlist) - 1, text)\n        else:\n            bot.send_msg(tr('bad_url'), text)\n\n\ndef cmd_rb_query(bot, user, text, command, parameter):\n    global log\n\n    log.info('cmd: Querying radio stations')\n    if not parameter:\n        log.debug('rbquery without parameter')\n        msg = tr('rb_query_empty')\n        bot.send_msg(msg, text)\n    else:\n        log.debug('cmd: Found query parameter: ' + parameter)\n        rb = RadioBrowser()\n        rb_stations = rb.search(name=parameter, name_exact=False)\n        msg = tr('rb_query_result')\n        msg += '\\n<table><tr><th>!rbplay ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th></tr>'\n        if not rb_stations:\n            log.debug(f\"cmd: No matches found for rbquery {parameter}\")\n            bot.send_msg(f\"Radio-Browser found no matches for {parameter}\", text)\n        else:\n            for s in rb_stations:\n                station_id = s['stationuuid']\n                station_name = s['name']\n                country = s['countrycode']\n                codec = s['codec']\n                bitrate = s['bitrate']\n                genre = s['tags']\n                msg += f\"<tr><td>{station_id}</td><td>{station_name}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td></tr>\"\n            msg += '</table>'\n            # Full message as html table\n            if len(msg) <= 5000:\n                bot.send_msg(msg, text)\n            # Shorten message if message too long (stage I)\n            else:\n                log.debug('Result too long stage I')\n                msg = tr('rb_query_result') + ' (shortened L1)'\n                msg += '\\n<table><tr><th>!rbplay ID</th><th>Station Name</th></tr>'\n                for s in rb_stations:\n                    station_id = s['stationuuid']\n                    station_name = s['name']\n                    msg += f'<tr><td>{station_id}</td><td>{station_name}</td>'\n                msg += '</table>'\n                if len(msg) <= 5000:\n                    bot.send_msg(msg, text)\n                # Shorten message if message too long (stage II)\n                else:\n                    log.debug('Result too long stage II')\n                    msg = tr('rb_query_result') + ' (shortened L2)'\n                    msg += '!rbplay ID - Station Name'\n                    for s in rb_stations:\n                        station_id = s['stationuuid']\n                        station_name = s['name'][:12]\n                        msg += f'{station_id} - {station_name}'\n                    if len(msg) <= 5000:\n                        bot.send_msg(msg, text)\n                    # Message still too long\n                    else:\n                        bot.send_msg('Query result too long to post (> 5000 characters), please try another query.', text)\n\n\ndef cmd_rb_play(bot, user, text, command, parameter):\n    global log\n\n    log.debug('cmd: Play a station by ID')\n    if not parameter:\n        log.debug('rbplay without parameter')\n        msg = tr('rb_play_empty')\n        bot.send_msg(msg, text)\n    else:\n        log.debug('cmd: Retreiving url for station ID ' + parameter)\n        rb = RadioBrowser()\n        rstation = rb.station_by_uuid(parameter)\n        stationname = rstation[0]['name']\n        country = rstation[0]['countrycode']\n        codec = rstation[0]['codec']\n        bitrate = rstation[0]['bitrate']\n        genre = rstation[0]['tags']\n        homepage = rstation[0]['homepage']\n        url = rstation[0]['url']\n        msg = 'Radio station added to playlist:'\n\n        msg += '<table><tr><th>ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th><th>Homepage</th></tr>' + \\\n               f\"<tr><td>{parameter}</td><td>{stationname}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td><td>{homepage}</td></tr></table>\"\n        log.debug(f'cmd: Added station to playlist {stationname}')\n        bot.send_msg(msg, text)\n        if url != \"-1\":\n            log.info('cmd: Found url: ' + url)\n            music_wrapper = get_cached_wrapper_from_scrap(type='radio', url=url, name=stationname, user=user)\n            var.playlist.append(music_wrapper)\n            log.info(\"cmd: add to playlist: \" + music_wrapper.format_debug_string())\n            bot.async_download_next()\n        else:\n            log.info('cmd: No playable url found.')\n            msg += \"No playable url found for this station, please try another station.\"\n            bot.send_msg(msg, text)\n\n\nyt_last_result = []\nyt_last_page = 0  # TODO: if we keep adding global variables, we need to consider sealing all commands up into classes.\n\n\ndef cmd_yt_search(bot, user, text, command, parameter):\n    global log, yt_last_result, yt_last_page, song_shortlist\n    item_per_page = 5\n\n    if parameter:\n        # if next page\n        if parameter.startswith(\"-n\"):\n            yt_last_page += 1\n            if len(yt_last_result) > yt_last_page * item_per_page:\n                song_shortlist = [{'type': 'url',\n                                   'url': \"https://www.youtube.com/watch?v=\" + result[0],\n                                   'title': result[1]\n                                   } for result in yt_last_result[yt_last_page * item_per_page: (yt_last_page * item_per_page) + item_per_page]]\n                msg = _yt_format_result(yt_last_result, yt_last_page * item_per_page, item_per_page)\n                bot.send_msg(tr('yt_result', result_table=msg), text)\n            else:\n                bot.send_msg(tr('yt_no_more'), text)\n\n        # if query\n        else:\n            results = util.youtube_search(parameter)\n            if results:\n                yt_last_result = results\n                yt_last_page = 0\n                song_shortlist = [{'type': 'url', 'url': \"https://www.youtube.com/watch?v=\" + result[0]}\n                                  for result in results[0: item_per_page]]\n                msg = _yt_format_result(results, 0, item_per_page)\n                bot.send_msg(tr('yt_result', result_table=msg), text)\n            else:\n                bot.send_msg(tr('yt_query_error'), text)\n    else:\n        bot.send_msg(tr('bad_parameter', command=command), text)\n\n\ndef _yt_format_result(results, start, count):\n    msg = '<table><tr><th width=\"10%\">Index</th><th>Title</th><th width=\"20%\">Uploader</th></tr>'\n    for index, item in enumerate(results[start:start + count]):\n        msg += '<tr><td>{index:d}</td><td>{title}</td><td>{uploader}</td></tr>'.format(\n            index=index + 1, title=item[1], uploader=item[2])\n    msg += '</table>'\n\n    return msg\n\n\ndef cmd_yt_play(bot, user, text, command, parameter):\n    global log, yt_last_result, yt_last_page\n\n    if parameter:\n        results = util.youtube_search(parameter)\n        if results:\n            yt_last_result = results\n            yt_last_page = 0\n            url = \"https://www.youtube.com/watch?v=\" + yt_last_result[0][0]\n            cmd_play_url(bot, user, text, command, url)\n        else:\n            bot.send_msg(tr('yt_query_error'), text)\n    else:\n        bot.send_msg(tr('bad_parameter', command=command), text)\n\n\ndef cmd_help(bot, user, text, command, parameter):\n    global log\n    bot.send_msg(tr('help'), text)\n    if bot.is_admin(user):\n        bot.send_msg(tr('admin_help'), text)\n\n\ndef cmd_stop(bot, user, text, command, parameter):\n    global log\n\n    if var.config.getboolean(\"bot\", \"clear_when_stop_in_oneshot\") \\\n            and var.playlist.mode == 'one-shot':\n        cmd_clear(bot, user, text, command, parameter)\n    else:\n        bot.stop()\n    bot.send_msg(tr('stopped'), text)\n\n\ndef cmd_clear(bot, user, text, command, parameter):\n    global log\n\n    bot.clear()\n    bot.send_msg(tr('cleared'), text)\n\n\ndef cmd_kill(bot, user, text, command, parameter):\n    global log\n\n    bot.pause()\n    bot.exit = True\n\n\ndef cmd_update(bot, user, text, command, parameter):\n    global log\n\n    if bot.is_admin(user):\n        bot.mumble.users[text.actor].send_text_message(\n            tr('start_updating'))\n        msg = util.update(bot.version)\n        bot.mumble.users[text.actor].send_text_message(msg)\n    else:\n        bot.mumble.users[text.actor].send_text_message(\n            tr('not_admin'))\n\n\ndef cmd_stop_and_getout(bot, user, text, command, parameter):\n    global log\n\n    bot.stop()\n    if var.playlist.mode == \"one-shot\":\n        var.playlist.clear()\n\n    bot.join_channel()\n\n\ndef cmd_volume(bot, user, text, command, parameter):\n    global log\n\n    # The volume is a percentage\n    max_vol = min(int(var.config.getfloat('bot', 'max_volume') * 100), 100.0)\n    if var.db.has_option('bot', 'max_volume'):\n        max_vol = float(var.db.get('bot', 'max_volume')) * 100.0\n    if parameter and parameter.isdigit() and 0 <= int(parameter) <= 100:\n        if int(parameter) <= max_vol:\n            vol = int(parameter)\n            bot.send_msg(tr('change_volume', volume=int(parameter), user=bot.mumble.users[text.actor]['name']), text)\n        else:\n            vol = max_vol\n            bot.send_msg(tr('max_volume', max=int(vol)), text)\n        bot.volume_helper.set_volume(float(vol) / 100.0)\n        var.db.set('bot', 'volume', str(float(vol) / 100.0))\n        log.info(f'cmd: volume set to {float(vol) / 100.0}')\n    else:\n        bot.send_msg(tr('current_volume', volume=int(bot.volume_helper.plain_volume_set * 100)), text)\n\ndef cmd_max_volume(bot, user, text, command, parameter):\n    global log\n    \n    if parameter and parameter.isdigit() and 0 <= int(parameter) <= 100:\n        max_vol = float(parameter) / 100.0\n        var.db.set('bot', 'max_volume', float(parameter) / 100.0)\n        bot.send_msg(tr('change_max_volume', max=parameter, user=bot.mumble.users[text.actor]['name']), text)\n        if int(bot.volume_helper.plain_volume_set) > max_vol:\n            bot.volume_helper.set_volume(max_vol)\n        log.info(f'cmd: max volume set to {max_vol}')\n    else:\n        max_vol = var.config.getfloat('bot', 'max_volume') * 100.0\n        if var.db.has_option('bot', 'max_volume'):\n            max_vol = var.db.getfloat('bot', 'max_volume') * 100.0\n        bot.send_msg(tr('current_max_volume', max=int(max_vol)), text)\n        \ndef cmd_ducking(bot, user, text, command, parameter):\n    global log\n\n    if parameter == \"\" or parameter == \"on\":\n        bot.is_ducking = True\n        var.db.set('bot', 'ducking', True)\n        bot.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_SOUNDRECEIVED, bot.ducking_sound_received)\n        bot.mumble.set_receive_sound(True)\n        log.info('cmd: ducking is on')\n        msg = \"Ducking on.\"\n        bot.send_msg(msg, text)\n    elif parameter == \"off\":\n        bot.is_ducking = False\n        bot.mumble.set_receive_sound(False)\n        var.db.set('bot', 'ducking', False)\n        msg = \"Ducking off.\"\n        log.info('cmd: ducking is off')\n        bot.send_msg(msg, text)\n\n\ndef cmd_ducking_threshold(bot, user, text, command, parameter):\n    global log\n\n    if parameter and parameter.isdigit():\n        bot.ducking_threshold = int(parameter)\n        var.db.set('bot', 'ducking_threshold', str(bot.ducking_threshold))\n        msg = f\"Ducking threshold set to {bot.ducking_threshold}.\"\n        bot.send_msg(msg, text)\n    else:\n        msg = f\"Current ducking threshold is {bot.ducking_threshold}.\"\n        bot.send_msg(msg, text)\n\n\ndef cmd_ducking_volume(bot, user, text, command, parameter):\n    global log\n\n    # The volume is a percentage\n    if parameter and parameter.isdigit() and 0 <= int(parameter) <= 100:\n        bot.volume_helper.set_ducking_volume(float(parameter) / 100.0)\n        bot.send_msg(tr('change_ducking_volume', volume=parameter, user=bot.mumble.users[text.actor]['name']), text)\n        var.db.set('bot', 'ducking_volume', float(parameter) / 100.0)\n        log.info(f'cmd: volume on ducking set to {parameter}')\n    else:\n        bot.send_msg(tr('current_ducking_volume', volume=int(bot.volume_helper.plain_ducking_volume_set * 100)), text)\n\n\ndef cmd_current_music(bot, user, text, command, parameter):\n    global log\n\n    if len(var.playlist) > 0:\n        bot.send_msg(var.playlist.current_item().format_current_playing(), text)\n    else:\n        bot.send_msg(tr('not_playing'), text)\n\n\ndef cmd_skip(bot, user, text, command, parameter):\n    global log\n\n    if not bot.is_pause:\n        bot.interrupt()\n    else:\n        var.playlist.next()\n        bot.wait_for_ready = True\n\n    if len(var.playlist) == 0:\n        bot.send_msg(tr('queue_empty'), text)\n\n\ndef cmd_last(bot, user, text, command, parameter):\n    global log\n\n    if len(var.playlist) > 0:\n        bot.interrupt()\n        var.playlist.point_to(len(var.playlist) - 1 - 1)\n    else:\n        bot.send_msg(tr('queue_empty'), text)\n\n\ndef cmd_remove(bot, user, text, command, parameter):\n    global log\n\n    # Allow to remove specific music into the queue with a number\n    if parameter and parameter.isdigit() and 0 < int(parameter) <= len(var.playlist):\n\n        index = int(parameter) - 1\n\n        if index == var.playlist.current_index:\n            removed = var.playlist[index]\n            bot.send_msg(tr('removing_item',\n                                      item=removed.format_title()), text)\n            log.info(\"cmd: delete from playlist: \" + removed.format_debug_string())\n\n            var.playlist.remove(index)\n\n            if index < len(var.playlist):\n                if not bot.is_pause:\n                    bot.interrupt()\n                    var.playlist.current_index -= 1\n                    # then the bot will move to next item\n\n            else:  # if item deleted is the last item of the queue\n                var.playlist.current_index -= 1\n                if not bot.is_pause:\n                    bot.interrupt()\n        else:\n            var.playlist.remove(index)\n\n    else:\n        bot.send_msg(tr('bad_parameter', command=command), text)\n\n\ndef cmd_list_file(bot, user, text, command, parameter):\n    global song_shortlist\n\n    files = var.music_db.query_music(Condition()\n                                     .and_equal('type', 'file')\n                                     .order_by('path'))\n\n    song_shortlist = files\n\n    msgs = [tr(\"multiple_file_found\") + \"<ul>\"]\n    try:\n        count = 0\n        for index, file in enumerate(files):\n            if parameter:\n                match = re.search(parameter, file['path'])\n                if not match:\n                    continue\n\n            count += 1\n            if count > ITEMS_PER_PAGE:\n                break\n            msgs.append(\"<li><b>{:d}</b> - <b>{:s}</b> ({:s})</li>\".format(index + 1, file['title'], file['path']))\n\n        if count != 0:\n            msgs.append(\"</ul>\")\n            if count > ITEMS_PER_PAGE:\n                msgs.append(tr(\"records_omitted\"))\n            msgs.append(tr(\"shortlist_instruction\"))\n            send_multi_lines(bot, msgs, text, \"\")\n        else:\n            bot.send_msg(tr(\"no_file\"), text)\n\n    except re.error as e:\n        msg = tr('wrong_pattern', error=str(e))\n        bot.send_msg(msg, text)\n\n\ndef cmd_queue(bot, user, text, command, parameter):\n    global log\n\n    if len(var.playlist) == 0:\n        msg = tr('queue_empty')\n        bot.send_msg(msg, text)\n    else:\n        msgs = [tr('queue_contents')]\n        for i, music in enumerate(var.playlist):\n            tags = ''\n            if len(music.item().tags) > 0:\n                tags = \"<sup>{}</sup>\".format(\", \".join(music.item().tags))\n            if i == var.playlist.current_index:\n                newline = \"<b style='color:orange'>{} ({}) {} </b> {}\".format(i + 1, music.display_type(),\n                                                                              music.format_title(), tags)\n            else:\n                newline = '<b>{}</b> ({}) {} {}'.format(i + 1, music.display_type(),\n                                                        music.format_title(), tags)\n\n            msgs.append(newline)\n\n        send_multi_lines(bot, msgs, text)\n\n\ndef cmd_random(bot, user, text, command, parameter):\n    global log\n\n    bot.interrupt()\n    var.playlist.randomize()\n\n\ndef cmd_repeat(bot, user, text, command, parameter):\n    global log\n\n    repeat = 1\n    if parameter and parameter.isdigit():\n        repeat = int(parameter)\n\n    music = var.playlist.current_item()\n    if music:\n        for _ in range(repeat):\n            var.playlist.insert(\n                var.playlist.current_index + 1,\n                music\n            )\n            log.info(\"bot: add to playlist: \" + music.format_debug_string())\n\n        bot.send_channel_msg(tr(\"repeat\", song=music.format_song_string(), n=str(repeat)))\n    else:\n        bot.send_msg(tr(\"queue_empty\"), text)\n\n\ndef cmd_mode(bot, user, text, command, parameter):\n    global log\n\n    if not parameter:\n        bot.send_msg(tr(\"current_mode\", mode=var.playlist.mode), text)\n        return\n    if parameter not in [\"one-shot\", \"repeat\", \"random\", \"autoplay\"]:\n        bot.send_msg(tr('unknown_mode', mode=parameter), text)\n    else:\n        var.db.set('playlist', 'playback_mode', parameter)\n        var.playlist = media.playlist.get_playlist(parameter, var.playlist)\n        log.info(f\"command: playback mode changed to {parameter}.\")\n        bot.send_msg(tr(\"change_mode\", mode=var.playlist.mode,\n                                  user=bot.mumble.users[text.actor]['name']), text)\n        if parameter == \"random\":\n            bot.interrupt()\n\n\ndef cmd_play_tags(bot, user, text, command, parameter):\n    if not parameter:\n        bot.send_msg(tr('bad_parameter', command=command), text)\n        return\n\n    msgs = [tr('multiple_file_added') + \"<ul>\"]\n    count = 0\n\n    tags = parameter.split(\",\")\n    tags = list(map(lambda t: t.strip(), tags))\n    music_wrappers = get_cached_wrappers_by_tags(tags, user)\n    for music_wrapper in music_wrappers:\n        count += 1\n        log.info(\"cmd: add to playlist: \" + music_wrapper.format_debug_string())\n        msgs.append(\"<li><b>{}</b> (<i>{}</i>)</li>\".format(music_wrapper.item().title, \", \".join(music_wrapper.item().tags)))\n\n    if count != 0:\n        msgs.append(\"</ul>\")\n        var.playlist.extend(music_wrappers)\n        send_multi_lines_in_channel(bot, msgs, \"\")\n    else:\n        bot.send_msg(tr(\"no_file\"), text)\n\n\ndef cmd_add_tag(bot, user, text, command, parameter):\n    global log\n\n    params = parameter.split(\" \", 1)\n    index = 0\n    tags = []\n\n    if len(params) == 2 and params[0].isdigit():\n        index = params[0]\n        tags = list(map(lambda t: t.strip(), params[1].split(\",\")))\n    elif len(params) == 2 and params[0] == \"*\":\n        index = \"*\"\n        tags = list(map(lambda t: t.strip(), params[1].split(\",\")))\n    else:\n        index = str(var.playlist.current_index + 1)\n        tags = list(map(lambda t: t.strip(), parameter.split(\",\")))\n\n    if tags[0]:\n        if index.isdigit() and 1 <= int(index) <= len(var.playlist):\n            var.playlist[int(index) - 1].add_tags(tags)\n            log.info(f\"cmd: add tags {', '.join(tags)} to song {var.playlist[int(index) - 1].format_debug_string()}\")\n            bot.send_msg(tr(\"added_tags\",\n                                      tags=\", \".join(tags),\n                                      song=var.playlist[int(index) - 1].format_title()), text)\n            return\n\n        elif index == \"*\":\n            for item in var.playlist:\n                item.add_tags(tags)\n                log.info(f\"cmd: add tags {', '.join(tags)} to song {item.format_debug_string()}\")\n            bot.send_msg(tr(\"added_tags_to_all\", tags=\", \".join(tags)), text)\n            return\n\n    bot.send_msg(tr('bad_parameter', command=command), text)\n\n\ndef cmd_remove_tag(bot, user, text, command, parameter):\n    global log\n\n    params = parameter.split(\" \", 1)\n    index = 0\n    tags = []\n\n    if len(params) == 2 and params[0].isdigit():\n        index = params[0]\n        tags = list(map(lambda t: t.strip(), params[1].split(\",\")))\n    elif len(params) == 2 and params[0] == \"*\":\n        index = \"*\"\n        tags = list(map(lambda t: t.strip(), params[1].split(\",\")))\n    else:\n        index = str(var.playlist.current_index + 1)\n        tags = list(map(lambda t: t.strip(), parameter.split(\",\")))\n\n    if tags[0]:\n        if index.isdigit() and 1 <= int(index) <= len(var.playlist):\n            if tags[0] != \"*\":\n                var.playlist[int(index) - 1].remove_tags(tags)\n                log.info(f\"cmd: remove tags {', '.join(tags)} from song {var.playlist[int(index) - 1].format_debug_string()}\")\n                bot.send_msg(tr(\"removed_tags\",\n                                          tags=\", \".join(tags),\n                                          song=var.playlist[int(index) - 1].format_title()), text)\n                return\n            else:\n                var.playlist[int(index) - 1].clear_tags()\n                log.info(f\"cmd: clear tags from song {var.playlist[int(index) - 1].format_debug_string()}\")\n                bot.send_msg(tr(\"cleared_tags\",\n                                          song=var.playlist[int(index) - 1].format_title()), text)\n                return\n\n        elif index == \"*\":\n            if tags[0] != \"*\":\n                for item in var.playlist:\n                    item.remove_tags(tags)\n                    log.info(f\"cmd: remove tags {', '.join(tags)} from song {item.format_debug_string()}\")\n                bot.send_msg(tr(\"removed_tags_from_all\", tags=\", \".join(tags)), text)\n                return\n            else:\n                for item in var.playlist:\n                    item.clear_tags()\n                    log.info(f\"cmd: clear tags from song {item.format_debug_string()}\")\n                bot.send_msg(tr(\"cleared_tags_from_all\"), text)\n                return\n\n    bot.send_msg(tr('bad_parameter', command=command), text)\n\n\ndef cmd_find_tagged(bot, user, text, command, parameter):\n    global song_shortlist\n\n    if not parameter:\n        bot.send_msg(tr('bad_parameter', command=command), text)\n        return\n\n    msgs = [tr('multiple_file_found') + \"<ul>\"]\n    count = 0\n\n    tags = parameter.split(\",\")\n    tags = list(map(lambda t: t.strip(), tags))\n\n    music_dicts = var.music_db.query_music_by_tags(tags)\n    song_shortlist = music_dicts\n\n    for i, music_dict in enumerate(music_dicts):\n        item = dict_to_item(music_dict)\n        count += 1\n        if count > ITEMS_PER_PAGE:\n            break\n        msgs.append(\"<li><b>{:d}</b> - <b>{}</b> (<i>{}</i>)</li>\".format(i + 1, item.title, \", \".join(item.tags)))\n\n    if count != 0:\n        msgs.append(\"</ul>\")\n        if count > ITEMS_PER_PAGE:\n            msgs.append(tr(\"records_omitted\"))\n        msgs.append(tr(\"shortlist_instruction\"))\n        send_multi_lines(bot, msgs, text, \"\")\n    else:\n        bot.send_msg(tr(\"no_file\"), text)\n\n\ndef cmd_search_library(bot, user, text, command, parameter):\n    global song_shortlist\n    if not parameter:\n        bot.send_msg(tr('bad_parameter', command=command), text)\n        return\n\n    msgs = [tr('multiple_file_found') + \"<ul>\"]\n    count = 0\n\n    _keywords = parameter.split(\" \")\n    keywords = []\n    for kw in _keywords:\n        if kw:\n            keywords.append(kw)\n\n    music_dicts = var.music_db.query_music_by_keywords(keywords)\n    if music_dicts:\n        items = dicts_to_items(music_dicts)\n        song_shortlist = music_dicts\n\n        if len(items) == 1:\n            music_wrapper = get_cached_wrapper(items[0], user)\n            var.playlist.append(music_wrapper)\n            log.info(\"cmd: add to playlist: \" + music_wrapper.format_debug_string())\n            send_item_added_message(bot, music_wrapper, len(var.playlist) - 1, text)\n        else:\n            for item in items:\n                count += 1\n                if count > ITEMS_PER_PAGE:\n                    break\n                if len(item.tags) > 0:\n                    msgs.append(\"<li><b>{:d}</b> - [{}] <b>{}</b> (<i>{}</i>)</li>\".format(count, item.display_type(), item.title, \", \".join(item.tags)))\n                else:\n                    msgs.append(\"<li><b>{:d}</b> - [{}] <b>{}</b> </li>\".format(count, item.display_type(), item.title, \", \".join(item.tags)))\n\n            if count != 0:\n                msgs.append(\"</ul>\")\n                if count > ITEMS_PER_PAGE:\n                    msgs.append(tr(\"records_omitted\"))\n                msgs.append(tr(\"shortlist_instruction\"))\n                send_multi_lines(bot, msgs, text, \"\")\n            else:\n                bot.send_msg(tr(\"no_file\"), text)\n    else:\n        bot.send_msg(tr(\"no_file\"), text)\n\n\ndef cmd_shortlist(bot, user, text, command, parameter):\n    global song_shortlist, log\n    if parameter.strip() == \"*\":\n        msgs = [tr('multiple_file_added') + \"<ul>\"]\n        music_wrappers = []\n        for kwargs in song_shortlist:\n            kwargs['user'] = user\n            music_wrapper = get_cached_wrapper_from_scrap(**kwargs)\n            music_wrappers.append(music_wrapper)\n            log.info(\"cmd: add to playlist: \" + music_wrapper.format_debug_string())\n            msgs.append(\"<li>[{}] <b>{}</b></li>\".format(music_wrapper.item().type, music_wrapper.item().title))\n\n        var.playlist.extend(music_wrappers)\n\n        msgs.append(\"</ul>\")\n        send_multi_lines_in_channel(bot, msgs, \"\")\n        return\n\n    try:\n        indexes = [int(i) for i in parameter.split(\" \")]\n    except ValueError:\n        bot.send_msg(tr('bad_parameter', command=command), text)\n        return\n\n    if len(indexes) > 1:\n        msgs = [tr('multiple_file_added') + \"<ul>\"]\n        music_wrappers = []\n        for index in indexes:\n            if 1 <= index <= len(song_shortlist):\n                kwargs = song_shortlist[index - 1]\n                kwargs['user'] = user\n                music_wrapper = get_cached_wrapper_from_scrap(**kwargs)\n                music_wrappers.append(music_wrapper)\n                log.info(\"cmd: add to playlist: \" + music_wrapper.format_debug_string())\n                msgs.append(\"<li>[{}] <b>{}</b></li>\".format(music_wrapper.item().type, music_wrapper.item().title))\n            else:\n                var.playlist.extend(music_wrappers)\n                bot.send_msg(tr('bad_parameter', command=command), text)\n                return\n\n        var.playlist.extend(music_wrappers)\n\n        msgs.append(\"</ul>\")\n        send_multi_lines_in_channel(bot, msgs, \"\")\n        return\n    elif len(indexes) == 1:\n        index = indexes[0]\n        if 1 <= index <= len(song_shortlist):\n            kwargs = song_shortlist[index - 1]\n            kwargs['user'] = user\n            music_wrapper = get_cached_wrapper_from_scrap(**kwargs)\n            var.playlist.append(music_wrapper)\n            log.info(\"cmd: add to playlist: \" + music_wrapper.format_debug_string())\n            send_item_added_message(bot, music_wrapper, len(var.playlist) - 1, text)\n            return\n\n    bot.send_msg(tr('bad_parameter', command=command), text)\n\n\ndef cmd_delete_from_library(bot, user, text, command, parameter):\n    global song_shortlist, log\n\n    if not var.config.getboolean(\"bot\", \"delete_allowed\"):\n        bot.mumble.users[text.actor].send_text_message(tr('not_admin'))\n        return\n\n    try:\n        indexes = [int(i) for i in parameter.split(\" \")]\n    except ValueError:\n        bot.send_msg(tr('bad_parameter', command=command), text)\n        return\n\n    if len(indexes) > 1:\n        msgs = [tr('multiple_file_added') + \"<ul>\"]\n        count = 0\n        for index in indexes:\n            if 1 <= index <= len(song_shortlist):\n                music_dict = song_shortlist[index - 1]\n                if 'id' in music_dict:\n                    music_wrapper = get_cached_wrapper_by_id(music_dict['id'], user)\n                    log.info(\"cmd: remove from library: \" + music_wrapper.format_debug_string())\n                    msgs.append(\"<li>[{}] <b>{}</b></li>\".format(music_wrapper.item().type, music_wrapper.item().title))\n                    var.playlist.remove_by_id(music_dict['id'])\n                    var.cache.free_and_delete(music_dict['id'])\n                    count += 1\n            else:\n                bot.send_msg(tr('bad_parameter', command=command), text)\n                return\n\n        if count == 0:\n            bot.send_msg(tr('bad_parameter', command=command), text)\n            return\n\n        msgs.append(\"</ul>\")\n        send_multi_lines_in_channel(bot, msgs, \"\")\n        return\n    elif len(indexes) == 1:\n        index = indexes[0]\n        if 1 <= index <= len(song_shortlist):\n            music_dict = song_shortlist[index - 1]\n            if 'id' in music_dict:\n                music_wrapper = get_cached_wrapper_by_id(music_dict['id'], user)\n                bot.send_msg(tr('file_deleted', item=music_wrapper.format_song_string()), text)\n                log.info(\"cmd: remove from library: \" + music_wrapper.format_debug_string())\n                var.playlist.remove_by_id(music_dict['id'])\n                var.cache.free_and_delete(music_dict['id'])\n                return\n\n    bot.send_msg(tr('bad_parameter', command=command), text)\n\n\ndef cmd_drop_database(bot, user, text, command, parameter):\n    global log\n\n    if bot.is_admin(user):\n        var.db.drop_table()\n        var.db = SettingsDatabase(var.settings_db_path)\n        var.music_db.drop_table()\n        var.music_db = MusicDatabase(var.settings_db_path)\n        log.info(\"command: database dropped.\")\n        bot.send_msg(tr('database_dropped'), text)\n    else:\n        bot.mumble.users[text.actor].send_text_message(tr('not_admin'))\n\n\ndef cmd_refresh_cache(bot, user, text, command, parameter):\n    global log\n    if bot.is_admin(user):\n        var.cache.build_dir_cache()\n        log.info(\"command: Local file cache refreshed.\")\n        bot.send_msg(tr('cache_refreshed'), text)\n    else:\n        bot.mumble.users[text.actor].send_text_message(tr('not_admin'))\n\n\ndef cmd_web_access(bot, user, text, command, parameter):\n    auth_method = var.config.get(\"webinterface\", \"auth_method\")\n\n    if auth_method == 'token':\n        interface.banned_ip = []\n        interface.bad_access_count = {}\n\n        user_info = var.db.get(\"user\", user, fallback='{}')\n        user_dict = json.loads(user_info)\n        if 'token' in user_dict:\n            var.db.remove_option(\"web_token\", user_dict['token'])\n\n        token = secrets.token_urlsafe(5)\n        user_dict['token'] = token\n        user_dict['token_created'] = str(datetime.datetime.now())\n        user_dict['last_ip'] = ''\n        var.db.set(\"web_token\", token, user)\n        var.db.set(\"user\", user, json.dumps(user_dict))\n\n        access_address = var.config.get(\"webinterface\", \"access_address\") + \"/?token=\" + token\n    else:\n        access_address = var.config.get(\"webinterface\", \"access_address\")\n\n    bot.send_msg(tr('webpage_address', address=access_address), text)\n\n\ndef cmd_user_password(bot, user, text, command, parameter):\n    if not parameter:\n        bot.send_msg(tr('bad_parameter', command=command), text)\n        return\n\n    user_info = var.db.get(\"user\", user, fallback='{}')\n    user_dict = json.loads(user_info)\n    user_dict['password'], user_dict['salt'] = util.get_salted_password_hash(parameter)\n\n    var.db.set(\"user\", user, json.dumps(user_dict))\n\n    bot.send_msg(tr('user_password_set'), text)\n\n\ndef cmd_web_user_add(bot, user, text, command, parameter):\n    if not parameter:\n        bot.send_msg(tr('bad_parameter', command=command), text)\n        return\n\n    auth_method = var.config.get(\"webinterface\", \"auth_method\")\n\n    if auth_method == 'password':\n        web_users = json.loads(var.db.get(\"privilege\", \"web_access\", fallback='[]'))\n        if parameter not in web_users:\n            web_users.append(parameter)\n        var.db.set(\"privilege\", \"web_access\", json.dumps(web_users))\n        bot.send_msg(tr('web_user_list', users=\", \".join(web_users)), text)\n    else:\n        bot.send_msg(tr('command_disabled', command=command), text)\n\n\ndef cmd_web_user_remove(bot, user, text, command, parameter):\n    if not parameter:\n        bot.send_msg(tr('bad_parameter', command=command), text)\n        return\n\n    auth_method = var.config.get(\"webinterface\", \"auth_method\")\n\n    if auth_method == 'password':\n        web_users = json.loads(var.db.get(\"privilege\", \"web_access\", fallback='[]'))\n        if parameter in web_users:\n            web_users.remove(parameter)\n        var.db.set(\"privilege\", \"web_access\", json.dumps(web_users))\n        bot.send_msg(tr('web_user_list', users=\", \".join(web_users)), text)\n    else:\n        bot.send_msg(tr('command_disabled', command=command), text)\n\n\ndef cmd_web_user_list(bot, user, text, command, parameter):\n    auth_method = var.config.get(\"webinterface\", \"auth_method\")\n\n    if auth_method == 'password':\n        web_users = json.loads(var.db.get(\"privilege\", \"web_access\", fallback='[]'))\n        bot.send_msg(tr('web_user_list', users=\", \".join(web_users)), text)\n    else:\n        bot.send_msg(tr('command_disabled', command=command), text)\n\n\ndef cmd_version(bot, user, text, command, parameter):\n    bot.send_msg(tr('report_version', version=bot.get_version()), text)\n\n\n# Just for debug use\ndef cmd_real_time_rms(bot, user, text, command, parameter):\n    bot._display_rms = not bot._display_rms\n\n\ndef cmd_loop_state(bot, user, text, command, parameter):\n    print(bot._loop_status)\n\n\ndef cmd_item(bot, user, text, command, parameter):\n    var.playlist._debug_print()\n"
  },
  {
    "path": "configuration.default.ini",
    "content": "# ========================================================\n#  botamusique Default Configuration File\n#  Version 6\n# ========================================================\n#  WARNING:\n#           ******************************\n#           ** DO NOT MODIFY THIS FILE. **\n#           ******************************\n#\n#           Please create your own configuration file, and\n#           ONLY ADD ITEMS YOU WANT TO MODIFY into it. Other\n#           items will be loaded from this file automatically.\n#           DO NOT DIRECTLY COPY THIS FILE.\n#\n#           That is because this file will be overridden\n#           during updates. New options will be added and\n#           old options (like [strings]) will be updated.\n# ========================================================\n\n[server]\ncertificate =\nchannel =\nhost = 127.0.0.1\npassword =\nport = 64738\ntokens =\n\n\n[bot]\nadmin =\nallow_other_channel_message = False\nallow_private_message = True\nannounce_current_music = True\nauto_check_update = True\nautoplay_length = 5\navatar =\nbandwidth = 96000\nclear_when_stop_in_oneshot = False\ncomment = \"Hi, I'm here to play radio, local music or youtube/soundcloud music. Have fun!\"\ndatabase_path =\ndelete_allowed = True\ndownload_attempts = 2\nducking = False\nducking_threshold = 3000\nducking_volume = 0.05\nignored_files = Thumbs.db\nignored_folders = tmp\nlanguage = en_US\nlogfile =\nmax_track_duration = 60\nmax_track_playlist = 20\nmax_volume = 1.0\nmusic_database_path = music.db\nmusic_folder = music_folder/\npip3_path = venv/bin/pip\nplayback_mode = one-shot\nredirect_stderr = True\nrefresh_cache_on_startup = True\nsave_music_library = True\nsave_playlist = True\nstereo = True\ntarget_version = git\ntmp_folder = /tmp/\ntmp_folder_max_size = 10\nusername = botamusique\nvolume = 0.8\nwhen_nobody_in_channel = nothing\nwhen_nobody_in_channel_ignore =\n\n[webinterface]\naccess_address = http://127.0.0.1:8181\nauth_method = none\nenabled = False\nflask_secret = ChangeThisPassword\nis_web_proxified = True\nlistening_addr = 127.0.0.1\nlistening_port = 8181\nmax_attempts = 10\nmax_upload_file_size = 30M\npassword =\nupload_enabled = True\nuser =\nweb_logfile =\n\n[debug]\nffmpeg = False\nmumble_connection = False\nredirect_ffmpeg_log = False\nyoutube_dl = False\n\n[radio]\nponyville = http://192.99.131.205:8000/stream.mp3 \"Here a command of !radio comment\"\nluna = http://radio.ponyvillelive.com:8002/stream \"calm and orchestra music\"\nradiobrony = http://62.210.138.34:8000/live \"Brony music of a friend\"\njazz = http://jazz-wr04.ice.infomaniak.ch/jazz-wr04-128.mp3 \"Jazz Yeah !\"\n\n[youtube_dl]\ncookie_file =\nsource_address =\nuser_agent =\n\n[commands]\nadd_from_shortlist = shortlist, sl\nadd_tag = addtag\nadd_webinterface_user = webuseradd\nchange_user_password = password\nclear = clear\ncommand_symbol = !:！\ncurrent_music = np, now\ndelete_from_library = delete\ndrop_database = dropdatabase\nducking = duck\nducking_threshold = duckthres\nducking_volume = duckv\nfind_tagged = findtagged, ft\nhelp = help\njoinme = joinme\nkill = kill\nlast = last\nlist_file = listfile\nlist_webinterface_user = webuserlist\nmax_volume = maxvolume\nmode = mode\npause = pause\nplay = p, play\nplay_file = file, f\nplay_file_match = filematch, fm\nplay_playlist = playlist\nplay_radio = radio\nplay_tag = tag\nplay_url = url\nqueue = queue\nrandom = random\nrb_play = rbplay\nrb_query = rbquery\nremove = rm\nremove_tag = untag\nremove_webinterface_user = webuserdel\nrepeat = repeat\nrequests_webinterface_access = web\nrescan = rescan\nsearch = search\nskip = skip\nsplit_username_at_space = False\nstop = stop\nstop_and_getout = oust\nupdate = update\nurl_ban = urlban\nurl_ban_list = urlbanlist\nurl_unban = urlunban\nurl_unwhitelist = urlunwhitelist, urlunw\nurl_whitelist = urlwhitelist, urlw\nurl_whitelist_list = urlwhitelistlist, urlwls\nuser_ban = userban\nuser_unban = userunban\nversion = version\nvolume = volume\nyt_play = yplay\nyt_search = ysearch\n\n"
  },
  {
    "path": "configuration.example.ini",
    "content": "# ========================================================\n#  botamusique example configuration file\n#  Version 6\n# ========================================================\n#  Rename this file to configuration.ini after editing.\n#  Uncomment lines you'd like to change, and carefully\n#  follow the instructions.\n# ========================================================\n\n# The [server] section tells the bot how to connect to your Murmur server.\n# This section will be overridden by command line arguments.\n[server]\nhost = 127.0.0.1\nport = 64738\n#password =\n#channel =\n#tokens = token1,token2\n#certificate =\n\n# The [bot] section stores some basic settings for the bot.\n[bot]\n# 'username': The bot's username.\n# 'comment': Comment displayed on the bot's profile.\n# 'avatar': Path to an image used for the bot's avatar (PNG recommended, 128 KB max).\n#username = botamusique\n#comment = \"Hi, I'm here to play radio, local music or youtube/soundcloud music. Have fun!\"\n#avatar =\n\n# 'language': Language to use; available languages can be found inside\n#    the lang/ folder.\n#language=en_US\n\n# 'music_folder': Folder that stores your local songs.\n#music_folder = music_folder/\n\n# 'database_path': The path of the database, which stores things like your\n#    volume set by the !volume command, your playback mode and your playlist,\n#    banned URLs, etc.\n#    This option will be overridden by command line arguments.\n# 'music_database_path': The path of the database that stores the music library.\n#    Can be disabled by setting 'save_music_library = False'\n#database_path=settings.db\n#music_database_path=music.db\n\n# 'admin': List of users allowed to kill the bot, or ban URLs.\n#    Separated by ';'.\n#admin = User1;User2;\n\n# 'stereo': Enable stereo stream transmission, supported since Mumble 1.4.0.\n#    If this is not enabled, the bot will downgrade stereo sound into mono.\n#stereo = True\n\n# 'volume': The default volume, a number from 0 to 1.\n#    This option will be overridden by the value set in the database.\n#volume = 0.1\n\n# 'bandwidth': The number of bits per second used by the bot when streaming audio.\n#    Enabling this option will allow you to set it higher than the default value.\n#    If the given value exceeds the server's bitrate, the bitrate used by the bot\n#    will match the server's.\n#bandwidth = 200000\n\n# 'playback_mode': The playback mode of the bot. It should be one of the below:\n#     one-shot: remove item once it has finished playing\n#     repeat: repeat the playlist\n#     random: randomize the order of the playlist\n#     autoplay: randomly pick a track from the music library\n#    This option will be overridden by the value set in the database.\n# 'autoplay_length': How many songs to fill the playlist with in autoplay mode.\n# 'clear_when_stop_in_oneshot': Whether to clear the playlist when stopping the\n#    bot in one-shot mode.\n#playback_mode = one-shot\n#autoplay_length = 5\n#clear_when_stop_in_oneshot = False\n\n# 'target_version': version to fetch when updating:\n#    stable: use the curl command to get stable releases\n#    testing: follow git master branch using the git command\n#target_version = stable\n\n# 'tmp_folder': Folder that music will be downloaded into.\n# 'tmp_folder_max_size': Maximum size of tmp_folder in MB, or 0 to not cache\n#    at all, or -1 for unlimited size\n# 'ignored_files', 'ignored_folders': Files and folders to ignore during scanning.\n#tmp_folder = /tmp/\n#tmp_folder_max_size = 10\n#ignored_folders = tmp\n#ignored_files = Thumbs.db\n\n# 'download_attempts': How many times to attempt a download.\n#download_attempts = 2\n\n# 'auto_check_update': Whether to check for updates every time the bot starts,\n#    and post the changelog after an update was applied.\n#auto_check_update = True\n#pip3_path = venv/bin/pip\n\n# 'logfile': File to write log messages to.\n# 'redirect_stderr': Whether to capture outputs from standard error and write\n#    it into the log file. Useful for capturing an exception message when the\n#    bot crashes.\n#logfile =\n#redirect_stderr = False\n\n#announce_current_music = True\n#allow_other_channel_message = False\n#allow_private_message = True\n\n# 'delete_allowed': Whether to allow admins to delete a file from the library\n#    stored on disk. Works for both command and web interfaces.\n#delete_allowed = True\n\n# 'save_music_library': Whether to save music metadata to the database.\n#save_music_library = True\n\n# 'refresh_cache_on_startup': Whether to refresh the music directory's cache when\n#    starting up. Metadata from each file will not be refreshed. If this is False,\n#    the cache from last time will be used.\n#refresh_cache_on_startup = True\n\n# 'save_playlist': Whether to save the current playlist before quitting, so that\n#    it may be reloaded next time. To use this, save_music_library must be True.\n#save_playlist = True\n\n# 'max_volume': Maximum volume users are allowed to set.\n#    Number between 0.0 - 1.0.\n#max_volume = 0.8\n\n# 'max_track_playlist': The maximum amount of tracks allowed in a playlist.\n#max_track_playlist = 20\n\n# 'max_track_duration': Maximum track duration in minutes.\n#max_track_duration = 60\n\n# 'ducking': Whether to lower music volume when someone is talking.\n#ducking = False\n#ducking_volume = 0.05\n#ducking_threshold = 3000\n\n# 'when_nobody_in_channel': Behaviour of the bot when nobody is in the channel.\n#    Has to be one of:\n#     pause: pause the current track\n#     pause_resume: pause the current track and resume it once someone joins\n#     stop: stop the bot, clearing its playlist\n#    Or you can leave it empty to take no action.\n#when_nobody_in_channel =\n\n# 'when_nobody_in_channel_ignore': List of users that should be ignored.\n#    This is typically used when other bots are present in the channel.\n#when_nobody_in_channel_ignore =\n\n# 'youtube_query_cookie': Sometimes YouTube will block the bot's request and ask\n#    the bot to complete a captcha to verify the request is made by a human. This\n#    can be solved if the bot has a valid cookie. If the bot complains \"unable to\n#    query youtube\", you should provide a value here.\n#youtube_query_cookie = {\"CONSENT\": \"paste your CONSENT cookie value here\"}\n\n# The [webinterface] section stores settings related to the web interface.\n[webinterface]\n# 'enabled': Whether to enable the web interface to allow managing your playlist,\n#    uploading tracks, etc.\n#    The web interface is disabled by default for security and performance reasons.\n# 'access_address': URL provided to users when the public URL for the\n#    web interface is requested.\n#enabled = False\n#listening_addr = 127.0.0.1\n#listening_port = 8181\n#is_web_proxified = True\n#access_address = http://127.0.0.1:8181\n\n# 'web_logfile': If this is provided, web server access logs are written to this file.\n#web_logfile =\n\n# 'auth_method': Method used to authenticate users accessing the web interface.\n#    One of 'none', 'password' or 'token'. If this is set to 'token', a unique token\n#    is used for authentication.\n# 'max_attempts': Amount of incorrect login attempts needed before being banned.\n#    Regenerating a token or rebooting the bot will reset this number.\n#auth_method = token\n#max_attempts = 10\n\n# 'user', 'password': If auth_method is set to 'password', you'll need to set\n#    the default username and password, which is set by these two options.\n#    You can add more users using the '!webadduser' command.\n#user = botamusique\n#password = mumble\n\n# 'flask_secret': To use a token, Flask needs a password to encrypt/sign cookies.\n#    This is absolutely necessary if auth_method is 'token'!\n#flask_secret = ChangeThisPassword\n\n# 'upload_enabled': Whether to enable the upload function of the web interface.\n#    If this is False, only admins can upload files.\n# 'maximum_upload_file_size': Maximum file size allowed for uploads.\n#    Can be specified in B, KB, MB, GB, or TB.\n#upload_enabled = True\n#max_upload_file_size = 30MB\n\n# The [debug] section contains settings to enable debugging messaages.\n[debug]\n# 'ffmpeg': Whether to display debug messages from ffmpeg.\n# 'mumble_connection': Whether to display debug messages for the\n#    connection to the Mumble server (from the pymumble library).\n# 'youtube_dl': Whether to display debug messages from youtube-dl.\n#ffmpeg = False\n#mumble_connection = False\n#youtube_dl = False\n\n# The [radio] section contains a list of default radio stations.\n[radio]\n# List of radio stations you want to have by default, one entry per line.\n#jazz = http://jazz-wr04.ice.infomaniak.ch/jazz-wr04-128.mp3 \"Jazz Yeah !\"\n\n# The optional [youtube_dl] section contains options to customize youtube-dl\n[youtube_dl]\n# 'source_address': Set to '::' to force ipv6, \"0.0.0.0\" to force ipv4,\n#    or else put the IP address you want to use here.\n# 'cookie_file': Path of the cookie file to use, useful if you are being rate limited:\n#    <https://github.com/ytdl-org/youtube-dl#http-error-429-too-many-requests-or-402-payment-required>\n# 'user_agent': Set the User-Agent header when making requests to youtube.com.\n# source_address = '::'\n# cookie_file = /tmp/youtube-dl-cookie\n# user-agent = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:87.0) Gecko/20100101 Firefox/87.0\"\n\n# The [commands] section contains settings related to user commands sent via\n# Mumble text messages.\n[commands]\n# 'command_symbol': List of characters recognized as a command prefix.\n# 'split_username_at_space': Whether usernames should be split by a space,\n#    in case you use these kinds of Mumo plugins:\n#    <https://wiki.mumble.info/wiki/Mumo#Set_Status>\n#split_username_at_space = False\n#command_symbol = !:！\n\n# You may also customize commands recognized by the bot. For a full list of commands,\n#    see configuration.default.ini. Copy options you want to edit into this file.\n#play_file = file, f\n#play_file_match = filematch, fm\n"
  },
  {
    "path": "constants.py",
    "content": "import os\nimport json\n\nimport variables as var\n\ndefault_lang_dict = {}\nlang_dict = {}\n\n\ndef load_lang(lang):\n    global lang_dict, default_lang_dict\n    root_dir = os.path.dirname(__file__)\n    with open(os.path.join(root_dir, \"lang/en_US.json\"), \"r\") as f:\n        default_lang_dict = json.load(f)\n    with open(os.path.join(root_dir, f\"lang/{lang}.json\"), \"r\") as f:\n        lang_dict = json.load(f)\n\n\ndef tr_cli(option, *argv, **kwargs):\n    try:\n        if option in lang_dict['cli'] and lang_dict['cli'][option]:\n            string = lang_dict['cli'][option]\n        else:\n            string = default_lang_dict['cli'][option]\n    except KeyError:\n        raise KeyError(\"Missed strings in language file: '{string}'. \".format(string=option))\n    return _tr(string, *argv, **kwargs)\n\n\ndef tr_web(option, *argv, **kwargs):\n    try:\n        if option in lang_dict['web'] and lang_dict['web'][option]:\n            string = lang_dict['web'][option]\n        else:\n            string = default_lang_dict['web'][option]\n    except KeyError:\n        raise KeyError(\"Missed strings in language file: '{string}'. \".format(string=option))\n    return _tr(string, *argv, **kwargs)\n\n\ndef _tr(string, *argv, **kwargs):\n    if argv or kwargs:\n        try:\n            formatted = string.format(*argv, **kwargs)\n            return formatted\n        except KeyError as e:\n            raise KeyError(\n                \"Missed/Unexpected placeholder {{{placeholder}}} in string \"\n                \"'{string}'. \".format(placeholder=str(e).strip(\"'\"),\n                                      string=string))\n        except TypeError:\n            raise KeyError(\n                \"Missed placeholder in string '{string}'. \".format(string=string))\n    else:\n        return string\n\n\ndef commands(command):\n    try:\n        string = var.config.get(\"commands\", command)\n        return string\n    except KeyError:\n        raise KeyError(\"Missed command in configuration file: '{string}'. \".format(string=command))\n"
  },
  {
    "path": "database.py",
    "content": "import os\nimport re\nimport sqlite3\nimport json\nimport datetime\nimport time\nimport logging\n\nlog = logging.getLogger(\"bot\")\n\n\nclass DatabaseError(Exception):\n    pass\n\n\nclass Condition:\n    def __init__(self):\n        self.filler = []\n        self._sql = \"\"\n        self._limit = 0\n        self._offset = 0\n        self._order_by = \"\"\n        self._desc = \"\"\n        self.has_regex = False\n        pass\n\n    def sql(self, conn: sqlite3.Connection = None):\n        sql = self._sql\n        if not self._sql:\n            sql = \"1\"\n        if self._order_by:\n            sql += f\" ORDER BY {self._order_by}\"\n            if self._desc:\n                sql += \" DESC\"\n        if self._limit:\n            sql += f\" LIMIT {self._limit}\"\n        if self._offset:\n            sql += f\" OFFSET {self._offset}\"\n        if self.has_regex and conn:\n            conn.create_function(\"REGEXP\", 2, self._regexp)\n\n        return sql\n\n    @staticmethod\n    def _regexp(expr, item):\n        if not item:\n            return False\n        reg = re.compile(expr)\n        return reg.search(item) is not None\n\n    def or_equal(self, column, equals_to, case_sensitive=True):\n        if not case_sensitive:\n            column = f\"LOWER({column})\"\n            equals_to = equals_to.lower()\n\n        if self._sql:\n            self._sql += f\" OR {column}=?\"\n        else:\n            self._sql += f\"{column}=?\"\n\n        self.filler.append(equals_to)\n\n        return self\n\n    def and_equal(self, column, equals_to, case_sensitive=True):\n        if not case_sensitive:\n            column = f\"LOWER({column})\"\n            equals_to = equals_to.lower()\n\n        if self._sql:\n            self._sql += f\" AND {column}=?\"\n        else:\n            self._sql += f\"{column}=?\"\n\n        self.filler.append(equals_to)\n\n        return self\n\n    def or_like(self, column, equals_to, case_sensitive=True):\n        if not case_sensitive:\n            column = f\"LOWER({column})\"\n            equals_to = equals_to.lower()\n\n        if self._sql:\n            self._sql += f\" OR {column} LIKE ?\"\n        else:\n            self._sql += f\"{column} LIKE ?\"\n\n        self.filler.append(equals_to)\n\n        return self\n\n    def and_like(self, column, equals_to, case_sensitive=True):\n        if not case_sensitive:\n            column = f\"LOWER({column})\"\n            equals_to = equals_to.lower()\n\n        if self._sql:\n            self._sql += f\" AND {column} LIKE ?\"\n        else:\n            self._sql += f\"{column} LIKE ?\"\n\n        self.filler.append(equals_to)\n\n        return self\n\n    def and_regexp(self, column, regex):\n        self.has_regex = True\n\n        if self._sql:\n            self._sql += f\" AND {column} REGEXP ?\"\n        else:\n            self._sql += f\"{column} REGEXP ?\"\n\n        self.filler.append(regex)\n\n        return self\n\n    def or_regexp(self, column, regex):\n        self.has_regex = True\n\n        if self._sql:\n            self._sql += f\" OR {column} REGEXP ?\"\n        else:\n            self._sql += f\"{column} REGEXP ?\"\n\n        self.filler.append(regex)\n\n        return self\n\n    def or_sub_condition(self, sub_condition):\n        if sub_condition.has_regex:\n            self.has_regex = True\n\n        self.filler.extend(sub_condition.filler)\n        if self._sql:\n            self._sql += f\" OR ({sub_condition.sql(None)})\"\n        else:\n            self._sql += f\"({sub_condition.sql(None)})\"\n\n        return self\n\n    def or_not_sub_condition(self, sub_condition):\n        if sub_condition.has_regex:\n            self.has_regex = True\n\n        self.filler.extend(sub_condition.filler)\n        if self._sql:\n            self._sql += f\" OR NOT ({sub_condition.sql(None)})\"\n        else:\n            self._sql += f\"NOT ({sub_condition.sql(None)})\"\n\n        return self\n\n    def and_sub_condition(self, sub_condition):\n        if sub_condition.has_regex:\n            self.has_regex = True\n\n        self.filler.extend(sub_condition.filler)\n        if self._sql:\n            self._sql += f\" AND ({sub_condition.sql(None)})\"\n        else:\n            self._sql += f\"({sub_condition.sql(None)})\"\n\n        return self\n\n    def and_not_sub_condition(self, sub_condition):\n        if sub_condition.has_regex:\n            self.has_regex = True\n\n        self.filler.extend(sub_condition.filler)\n        if self._sql:\n            self._sql += f\" AND NOT({sub_condition.sql(None)})\"\n        else:\n            self._sql += f\"NOT ({sub_condition.sql(None)})\"\n\n        return self\n\n    def limit(self, limit):\n        self._limit = limit\n\n        return self\n\n    def offset(self, offset):\n        self._offset = offset\n\n        return self\n\n    def order_by(self, order_by, desc=False):\n        self._order_by = order_by\n        self._desc = desc\n\n        return self\n\n\nSETTING_DB_VERSION = 2\nMUSIC_DB_VERSION = 4\n\n\nclass SettingsDatabase:\n    def __init__(self, db_path):\n        self.db_path = db_path\n\n    def get(self, section, option, **kwargs):\n        conn = sqlite3.connect(self.db_path)\n        cursor = conn.cursor()\n        result = cursor.execute(\"SELECT value FROM botamusique WHERE section=? AND option=?\",\n                                (section, option)).fetchall()\n        conn.close()\n\n        if len(result) > 0:\n            return result[0][0]\n        else:\n            if 'fallback' in kwargs:\n                return kwargs['fallback']\n            else:\n                raise DatabaseError(\"Item not found\")\n\n    def getboolean(self, section, option, **kwargs):\n        return bool(int(self.get(section, option, **kwargs)))\n\n    def getfloat(self, section, option, **kwargs):\n        return float(self.get(section, option, **kwargs))\n\n    def getint(self, section, option, **kwargs):\n        return int(self.get(section, option, **kwargs))\n\n    def set(self, section, option, value):\n        conn = sqlite3.connect(self.db_path)\n        cursor = conn.cursor()\n        cursor.execute(\"INSERT OR REPLACE INTO botamusique (section, option, value) \"\n                       \"VALUES (?, ?, ?)\", (section, option, value))\n        conn.commit()\n        conn.close()\n\n    def has_option(self, section, option):\n        conn = sqlite3.connect(self.db_path)\n        cursor = conn.cursor()\n        result = cursor.execute(\"SELECT value FROM botamusique WHERE section=? AND option=?\",\n                                (section, option)).fetchall()\n        conn.close()\n        if len(result) > 0:\n            return True\n        else:\n            return False\n\n    def remove_option(self, section, option):\n        conn = sqlite3.connect(self.db_path)\n        cursor = conn.cursor()\n        cursor.execute(\"DELETE FROM botamusique WHERE section=? AND option=?\", (section, option))\n        conn.commit()\n        conn.close()\n\n    def remove_section(self, section):\n        conn = sqlite3.connect(self.db_path)\n        cursor = conn.cursor()\n        cursor.execute(\"DELETE FROM botamusique WHERE section=?\", (section,))\n        conn.commit()\n        conn.close()\n\n    def items(self, section):\n        conn = sqlite3.connect(self.db_path)\n        cursor = conn.cursor()\n        results = cursor.execute(\"SELECT option, value FROM botamusique WHERE section=?\", (section,)).fetchall()\n        conn.close()\n\n        if len(results) > 0:\n            return list(map(lambda v: (v[0], v[1]), results))\n        else:\n            return []\n\n    def drop_table(self):\n        conn = sqlite3.connect(self.db_path)\n        cursor = conn.cursor()\n        cursor.execute(\"DROP TABLE botamusique\")\n        conn.close()\n\n\nclass MusicDatabase:\n    def __init__(self, db_path):\n        self.db_path = db_path\n\n    def insert_music(self, music_dict, _conn=None):\n        conn = sqlite3.connect(self.db_path) if _conn is None else _conn\n        cursor = conn.cursor()\n\n        id = music_dict['id']\n        title = music_dict['title']\n        type = music_dict['type']\n        path = music_dict['path'] if 'path' in music_dict else ''\n        keywords = music_dict['keywords']\n\n        tags_list = list(dict.fromkeys(music_dict['tags']))\n        tags = ''\n        if tags_list:\n            tags = \",\".join(tags_list) + \",\"\n\n        del music_dict['id']\n        del music_dict['title']\n        del music_dict['type']\n        del music_dict['tags']\n        if 'path' in music_dict:\n            del music_dict['path']\n        del music_dict['keywords']\n\n        existed = cursor.execute(\"SELECT 1 FROM music WHERE id=?\", (id,)).fetchall()\n        if len(existed) == 0:\n            cursor.execute(\n                \"INSERT INTO music (id, type, title, metadata, tags, path, keywords) VALUES (?, ?, ?, ?, ?, ?, ?)\",\n                (id,\n                 type,\n                 title,\n                 json.dumps(music_dict),\n                 tags,\n                 path,\n                 keywords))\n        else:\n            cursor.execute(\"UPDATE music SET type=:type, title=:title, metadata=:metadata, tags=:tags, \"\n                           \"path=:path, keywords=:keywords WHERE id=:id\",\n                           {'id': id,\n                            'type': type,\n                            'title': title,\n                            'metadata': json.dumps(music_dict),\n                            'tags': tags,\n                            'path': path,\n                            'keywords': keywords})\n\n        if not _conn:\n            conn.commit()\n            conn.close()\n\n    def query_music_ids(self, condition: Condition):\n        conn = sqlite3.connect(self.db_path)\n        cursor = conn.cursor()\n        results = cursor.execute(\"SELECT id FROM music WHERE id != 'info' AND %s\" %\n                                 condition.sql(conn), condition.filler).fetchall()\n        conn.close()\n        return list(map(lambda i: i[0], results))\n\n    def query_all_paths(self):\n        conn = sqlite3.connect(self.db_path)\n        cursor = conn.cursor()\n        results = cursor.execute(\"SELECT path FROM music WHERE id != 'info' AND type = 'file'\").fetchall()\n        conn.close()\n        paths = []\n        for result in results:\n            if result and result[0]:\n                paths.append(result[0])\n\n        return paths\n\n    def query_all_tags(self):\n        conn = sqlite3.connect(self.db_path)\n        cursor = conn.cursor()\n        results = cursor.execute(\"SELECT tags FROM music WHERE id != 'info'\").fetchall()\n        tags = []\n        for result in results:\n            for tag in result[0].strip(\",\").split(\",\"):\n                if tag and tag not in tags:\n                    tags.append(tag)\n        conn.close()\n        return tags\n\n    def query_music_count(self, condition: Condition):\n        filler = condition.filler\n\n        conn = sqlite3.connect(self.db_path)\n        condition_str = condition.sql(conn)\n        cursor = conn.cursor()\n        results = cursor.execute(\"SELECT COUNT(*) FROM music \"\n                                 \"WHERE id != 'info' AND %s\" % condition_str, filler).fetchall()\n        conn.close()\n\n        return results[0][0]\n\n    def query_music(self, condition: Condition, _conn=None):\n        filler = condition.filler\n\n        conn = sqlite3.connect(self.db_path) if _conn is None else _conn\n        condition_str = condition.sql(conn)\n        cursor = conn.cursor()\n        results = cursor.execute(\"SELECT id, type, title, metadata, tags, path, keywords FROM music \"\n                                 \"WHERE id != 'info' AND %s\" % condition_str, filler).fetchall()\n        if not _conn:\n            conn.close()\n\n        return self._result_to_dict(results)\n\n    def _query_music_by_plain_sql_cond(self, sql_cond, _conn=None):\n        conn = sqlite3.connect(self.db_path) if _conn is None else _conn\n        cursor = conn.cursor()\n        results = cursor.execute(\"SELECT id, type, title, metadata, tags, path, keywords FROM music \"\n                                 \"WHERE id != 'info' AND %s\" % sql_cond).fetchall()\n        if not _conn:\n            conn.close()\n\n        return self._result_to_dict(results)\n\n    def query_music_by_id(self, _id, _conn=None):\n        results = self.query_music(Condition().and_equal(\"id\", _id), _conn)\n        if results:\n            return results[0]\n        else:\n            return None\n\n    def query_music_by_keywords(self, keywords, _conn=None):\n        condition = Condition()\n\n        for keyword in keywords:\n            condition.and_like(\"title\", f\"%{keyword}%\", case_sensitive=False)\n\n        return self.query_music(condition, _conn)\n\n    def query_music_by_tags(self, tags, _conn=None):\n        condition = Condition()\n\n        for tag in tags:\n            condition.and_like(\"tags\", f\"%{tag},%\", case_sensitive=False)\n\n        return self.query_music(condition, _conn)\n\n    def manage_special_tags(self):\n        conn = sqlite3.connect(self.db_path)\n        cursor = conn.cursor()\n        cursor.execute(\"UPDATE music SET tags=REPLACE(tags, 'recent added,', '') WHERE tags LIKE '%recent added,%' \"\n                       \"AND create_at <= DATETIME('now', '-1 day') AND id != 'info'\")\n        cursor.execute(\"UPDATE music SET tags=tags||'recent added,' WHERE tags NOT LIKE '%recent added,%' \"\n                       \"AND create_at > DATETIME('now', '-1 day') AND id != 'info'\")\n        conn.commit()\n        conn.close()\n\n    def query_tags(self, condition: Condition):\n        # TODO: Can we keep a index of tags?\n        conn = sqlite3.connect(self.db_path)\n        cursor = conn.cursor()\n        results = cursor.execute(\"SELECT id, tags FROM music \"\n                                 \"WHERE id != 'info' AND %s\" % condition.sql(conn), condition.filler).fetchall()\n\n        conn.close()\n\n        lookup = {}\n        if len(results) > 0:\n            for result in results:\n                id = result[0]\n                tags = result[1].strip(\",\").split(\",\")\n                lookup[id] = tags if tags[0] else []\n\n        return lookup\n\n    def query_random_music(self, count, condition: Condition = None):\n        conn = sqlite3.connect(self.db_path)\n        cursor = conn.cursor()\n        results = []\n\n        if condition is None:\n            condition = Condition().and_not_sub_condition(Condition().and_equal('id', 'info'))\n\n        results = cursor.execute(\"SELECT id, type, title, metadata, tags, path, keywords FROM music \"\n                                 \"WHERE id IN (SELECT id FROM music WHERE %s ORDER BY RANDOM() LIMIT ?) \"\n                                 \"ORDER BY RANDOM()\"\n                                 % condition.sql(conn), condition.filler + [count]).fetchall()\n        conn.close()\n\n        return self._result_to_dict(results)\n\n    def _result_to_dict(self, results):\n        if len(results) > 0:\n            music_dicts = []\n            for result in results:\n                music_dict = json.loads(result[3])\n                music_dict['type'] = result[1]\n                music_dict['title'] = result[2]\n                music_dict['id'] = result[0]\n                music_dict['tags'] = result[4].strip(\",\").split(\",\") if result[4] else []\n                music_dict['path'] = result[5]\n                music_dict['keywords'] = result[6]\n\n                music_dicts.append(music_dict)\n\n            return music_dicts\n        else:\n            return []\n\n    def delete_music(self, condition: Condition):\n        conn = sqlite3.connect(self.db_path)\n        cursor = conn.cursor()\n        cursor.execute(\"DELETE FROM music \"\n                       \"WHERE %s\" % condition.sql(conn), condition.filler)\n        conn.commit()\n        conn.close()\n\n    def drop_table(self):\n        conn = sqlite3.connect(self.db_path)\n        cursor = conn.cursor()\n        cursor.execute(\"DROP TABLE music\")\n        conn.close()\n\n\nclass DatabaseMigration:\n    def __init__(self, settings_db: SettingsDatabase, music_db: MusicDatabase):\n        self.settings_db = settings_db\n        self.music_db = music_db\n        self.settings_table_migrate_func = {0: self.settings_table_migrate_from_0_to_1,\n                                            1: self.settings_table_migrate_from_1_to_2}\n        self.music_table_migrate_func = {0: self.music_table_migrate_from_0_to_1,\n                                         1: self.music_table_migrate_from_1_to_2,\n                                         2: self.music_table_migrate_from_2_to_4,\n                                         3: self.music_table_migrate_from_2_to_4\n                                         }\n\n\n    def migrate(self):\n        self.settings_database_migrate()\n        self.music_database_migrate()\n\n    def settings_database_migrate(self):\n        conn = sqlite3.connect(self.settings_db.db_path)\n        cursor = conn.cursor()\n        if self.has_table('botamusique', conn):\n            current_version = 0\n            ver = cursor.execute(\"SELECT value FROM botamusique WHERE section='bot' \"\n                                 \"AND option='db_version'\").fetchone()\n            if ver:\n                current_version = int(ver[0])\n\n            if current_version == SETTING_DB_VERSION:\n                conn.close()\n                return\n            else:\n                log.info(\n                    f\"database: migrating from settings table version {current_version} to {SETTING_DB_VERSION}...\")\n                while current_version < SETTING_DB_VERSION:\n                    log.debug(f\"database: migrate step {current_version}/{SETTING_DB_VERSION - 1}\")\n                    current_version = self.settings_table_migrate_func[current_version](conn)\n                log.info(f\"database: migration done.\")\n\n                cursor.execute(\"UPDATE botamusique SET value=? \"\n                               \"WHERE section='bot' AND option='db_version'\", (SETTING_DB_VERSION,))\n\n        else:\n            log.info(f\"database: no settings table found. Creating settings table version {SETTING_DB_VERSION}.\")\n            self.create_settings_table_version_2(conn)\n\n        conn.commit()\n        conn.close()\n\n    def music_database_migrate(self):\n        conn = sqlite3.connect(self.music_db.db_path)\n        cursor = conn.cursor()\n        if self.has_table('music', conn):\n            current_version = 0\n            ver = cursor.execute(\"SELECT title FROM music WHERE id='info'\").fetchone()\n            if ver:\n                current_version = int(ver[0])\n\n            if current_version == MUSIC_DB_VERSION:\n                conn.close()\n                return\n            else:\n                log.info(f\"database: migrating from music table version {current_version} to {MUSIC_DB_VERSION}...\")\n                while current_version < MUSIC_DB_VERSION:\n                    log.debug(f\"database: migrate step {current_version}/{MUSIC_DB_VERSION - 1}\")\n                    current_version = self.music_table_migrate_func[current_version](conn)\n                log.info(f\"database: migration done.\")\n\n                cursor.execute(\"UPDATE music SET title=? \"\n                               \"WHERE id='info'\", (MUSIC_DB_VERSION,))\n\n        else:\n            log.info(f\"database: no music table found. Creating music table version {MUSIC_DB_VERSION}.\")\n            self.create_music_table_version_4(conn)\n\n        conn.commit()\n        conn.close()\n\n    def has_table(self, table, conn):\n        cursor = conn.cursor()\n        tables = cursor.execute(\"SELECT name FROM sqlite_master WHERE type='table' AND name=?;\", (table,)).fetchall()\n        if len(tables) == 0:\n            return False\n        return True\n\n    def create_settings_table_version_2(self, conn):\n        cursor = conn.cursor()\n        cursor.execute(\"CREATE TABLE IF NOT EXISTS botamusique (\"\n                       \"section TEXT, \"\n                       \"option TEXT, \"\n                       \"value TEXT, \"\n                       \"UNIQUE(section, option))\")\n        cursor.execute(\"INSERT INTO botamusique (section, option, value) \"\n                       \"VALUES (?, ?, ?)\", (\"bot\", \"db_version\", 2))\n        conn.commit()\n\n        return 1\n\n    def create_music_table_version_1(self, conn):\n        cursor = conn.cursor()\n\n        cursor.execute(\"CREATE TABLE music (\"\n                       \"id TEXT PRIMARY KEY, \"\n                       \"type TEXT, \"\n                       \"title TEXT, \"\n                       \"keywords TEXT, \"\n                       \"metadata TEXT, \"\n                       \"tags TEXT, \"\n                       \"path TEXT, \"\n                       \"create_at DATETIME DEFAULT CURRENT_TIMESTAMP\"\n                       \")\")\n        cursor.execute(\"INSERT INTO music (id, title) \"\n                       \"VALUES ('info', ?)\", (MUSIC_DB_VERSION,))\n\n        conn.commit()\n\n    def create_music_table_version_4(self, conn):\n        self.create_music_table_version_1(conn)\n\n    def settings_table_migrate_from_0_to_1(self, conn):\n        cursor = conn.cursor()\n        cursor.execute(\"DROP TABLE botamusique\")\n        conn.commit()\n        self.create_settings_table_version_2(conn)\n        return 2  # return new version number\n\n    def settings_table_migrate_from_1_to_2(self, conn):\n        cursor = conn.cursor()\n        # move music database into a separated file\n        if self.has_table('music', conn) and not os.path.exists(self.music_db.db_path):\n            log.info(f\"database: move music db into separated file.\")\n            cursor.execute(f\"ATTACH DATABASE '{self.music_db.db_path}' AS music_db\")\n            cursor.execute(f\"SELECT sql FROM sqlite_master \"\n                           f\"WHERE type='table' AND name='music'\")\n            sql_create_table = cursor.fetchone()[0]\n            sql_create_table = sql_create_table.replace(\"music\", \"music_db.music\")\n            cursor.execute(sql_create_table)\n            cursor.execute(\"INSERT INTO music_db.music SELECT * FROM music\")\n            conn.commit()\n            cursor.execute(\"DETACH DATABASE music_db\")\n\n            cursor.execute(\"DROP TABLE music\")\n\n        cursor.execute(\"UPDATE botamusique SET value=2 \"\n                       \"WHERE section='bot' AND option='db_version'\")\n        return 2  # return new version number\n\n    def music_table_migrate_from_0_to_1(self, conn):\n        cursor = conn.cursor()\n        cursor.execute(\"ALTER TABLE music RENAME TO music_old\")\n        conn.commit()\n\n        self.create_music_table_version_1(conn)\n\n        cursor.execute(\"INSERT INTO music (id, type, title, metadata, tags)\"\n                       \"SELECT id, type, title, metadata, tags FROM music_old\")\n        cursor.execute(\"DROP TABLE music_old\")\n        conn.commit()\n\n        return 1  # return new version number\n\n    def music_table_migrate_from_1_to_2(self, conn):\n        items_to_update = self.music_db.query_music(Condition(), conn)\n        for item in items_to_update:\n            item['keywords'] = item['title']\n            if 'artist' in item:\n                item['keywords'] += ' ' + item['artist']\n\n            tags = []\n            for tag in item['tags']:\n                if tag:\n                    tags.append(tag)\n            item['tags'] = tags\n\n            self.music_db.insert_music(item)\n        conn.commit()\n\n        return 2  # return new version number\n\n    def music_table_migrate_from_2_to_4(self, conn):\n        items_to_update = self.music_db.query_music(Condition(), conn)\n        for item in items_to_update:\n            if 'duration' not in item:\n                item['duration'] = 0\n            if item['type'] == 'url' or item['type'] == \"url_from_playlist\":\n                item['duration'] = item['duration'] * 60\n\n            self.music_db.insert_music(item)\n        conn.commit()\n\n        return 4  # return new version number\n"
  },
  {
    "path": "entrypoint.sh",
    "content": "#!/usr/bin/env bash\ncommand=( \"${@}\" )\n\nif [ \"$1\" == \"bash\" ] || [ \"$1\" == \"sh\" ]; then\n    exec \"${@}\"\nfi\n\nif [ -n \"$BAM_DB\" ]; then\n    command+=( \"--db\" \"$BAM_DB\" )\nfi\n\nif [ -n \"$BAM_MUSIC_DB\" ]; then\n    command+=( \"--music-db\" \"$BAM_MUSIC_DB\" )\nfi\n\nif [ -n \"$BAM_MUMBLE_SERVER\" ]; then\n    command+=( \"--server\" \"$BAM_MUMBLE_SERVER\")\nfi\n\nif [ -n \"$BAM_MUMBLE_PASSWORD\" ]; then\n    command+=( \"--password\" \"$BAM_MUMBLE_PASSWORD\" )\nfi\n\nif [ -n \"$BAM_MUMBLE_PORT\" ]; then\n    command+=( \"--port\" \"$BAM_MUMBLE_PORT\" )\nfi\n\nif [ -n \"$BAM_USER\" ]; then\n    command+=( \"--user\" \"$BAM_USER\" )\nfi\n\nif [ -n \"$BAM_TOKENS\" ]; then\n    command+=( \"--tokens\" \"$BAM_TOKENS\" )\nfi\n\nif [ -n \"$BAM_CHANNEL\" ]; then\n    command+=( \"--channel\" \"$BAM_CHANNEL\" )\nfi\n\nif [ -n \"$BAM_CERTIFICATE\" ]; then\n    command+=( \"--cert\" \"$BAM_CERTIFICATE\" )\nfi\n\nif [ -n \"$BAM_VERBOSE\" ]; then\n    command+=( \"--verbose\" )\nfi\n\nif [ -n \"$BAM_BANDWIDTH\" ]; then\n    command+=( \"--bandwidth\" \"$BAM_BANDWIDTH\")\nfi\n\nif [ -n \"$BAM_CONFIG_file\" ]; then\n    if [ ! -f \"$BAM_CONFIG_file\" ]; then\n        cp \"/botamusique/configuration.example.ini\" \"$BAM_CONFIG_file\"\n    fi\n    command+=( \"--config\" \"$BAM_CONFIG_file\" )\nelse\n    if [ ! -f \"/botamusique/configuration.ini\" ]; then\n        cp \"/botamusique/configuration.example.ini\" \"/botamusique/configuration.ini\"\n    fi\n    command+=( \"--config\" \"/botamusique/configuration.ini\" )\nfi\n\nexec \"${command[@]}\"\n"
  },
  {
    "path": "interface.py",
    "content": "#!/usr/bin/python3\nimport sqlite3\nfrom functools import wraps\nfrom flask import Flask, render_template, request, redirect, send_file, Response, jsonify, abort, session\nfrom werkzeug.utils import secure_filename\n\nimport variables as var\nimport util\nimport math\nimport os\nimport os.path\nimport errno\nfrom typing import Type\nimport media\nimport json\nfrom media.item import dicts_to_items, dict_to_item, BaseItem\nfrom media.file import FileItem\nfrom media.url import URLItem\nfrom media.url_from_playlist import PlaylistURLItem\nfrom media.radio import RadioItem\nfrom media.cache import get_cached_wrapper_from_scrap, get_cached_wrapper_by_id, get_cached_wrappers_by_tags, \\\n    get_cached_wrapper\nfrom database import MusicDatabase, Condition\nimport logging\nimport time\n\n\nclass ReverseProxied(object):\n    \"\"\"Wrap the application in this middleware and configure the\n    front-end server to add these headers, to let you quietly bind\n    this to a URL other than / and to an HTTP scheme that is\n    different than what is used locally.\n\n    In nginx:\n    location /myprefix {\n        proxy_pass http://192.168.0.1:5001;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Scheme $scheme;\n        proxy_set_header X-Script-Name /myprefix;\n        }\n\n    :param app: the WSGI application\n    \"\"\"\n\n    def __init__(self, app):\n        self.app = app\n\n    def __call__(self, environ, start_response):\n        script_name = environ.get('HTTP_X_SCRIPT_NAME', '')\n        if script_name:\n            environ['SCRIPT_NAME'] = script_name\n            path_info = environ['PATH_INFO']\n            if path_info.startswith(script_name):\n                environ['PATH_INFO'] = path_info[len(script_name):]\n\n        scheme = environ.get('HTTP_X_SCHEME', '')\n        if scheme:\n            environ['wsgi.url_scheme'] = scheme\n        real_ip = environ.get('HTTP_X_REAL_IP', '')\n        if real_ip:\n            environ['REMOTE_ADDR'] = real_ip\n        return self.app(environ, start_response)\n\n\nroot_dir = os.path.dirname(__file__)\nweb = Flask(__name__, template_folder=os.path.join(root_dir, \"web/templates\"))\n#web.config['TEMPLATES_AUTO_RELOAD'] = True\nlog = logging.getLogger(\"bot\")\nuser = 'Remote Control'\n\n\ndef init_proxy():\n    global web\n    if var.is_proxified:\n        web.wsgi_app = ReverseProxied(web.wsgi_app)\n\n\n# https://stackoverflow.com/questions/29725217/password-protect-one-webpage-in-flask-app\n\n\ndef check_auth(username, password):\n    \"\"\"This function is called to check if a username /\n    password combination is valid.\n    \"\"\"\n\n    if username == var.config.get(\"webinterface\", \"user\") and password == var.config.get(\"webinterface\", \"password\"):\n        return True\n\n    web_users = json.loads(var.db.get(\"privilege\", \"web_access\", fallback='[]'))\n    if username in web_users:\n        user_dict = json.loads(var.db.get(\"user\", username, fallback='{}'))\n        if 'password' in user_dict and 'salt' in user_dict and \\\n                util.verify_password(password, user_dict['password'], user_dict['salt']):\n            return True\n\n    return False\n\n\ndef authenticate():\n    \"\"\"Sends a 401 response that enables basic auth\"\"\"\n    global log\n    return Response('Could not verify your access level for that URL.\\n'\n                    'You have to login with proper credentials', 401,\n                    {'WWW-Authenticate': 'Basic realm=\"Login Required\"'})\n\n\nbad_access_count = {}\nbanned_ip = []\n\n\ndef requires_auth(f):\n    @wraps(f)\n    def decorated(*args, **kwargs):\n        global log, user, bad_access_count, banned_ip\n\n        if request.remote_addr in banned_ip:\n            abort(403)\n\n        auth_method = var.config.get(\"webinterface\", \"auth_method\")\n\n        if auth_method == 'password':\n            auth = request.authorization\n            if auth:\n                user = auth.username\n                if not check_auth(auth.username, auth.password):\n                    if request.remote_addr in bad_access_count:\n                        bad_access_count[request.remote_addr] += 1\n                        log.info(f\"web: failed login attempt, user: {auth.username}, from ip {request.remote_addr}.\"\n                                 f\"{bad_access_count[request.remote_addr]} attempts.\")\n                        if bad_access_count[request.remote_addr] > var.config.getint(\"webinterface\", \"max_attempts\",\n                                                                                     fallback=10):\n                            banned_ip.append(request.remote_addr)\n                            log.info(f\"web: access banned for {request.remote_addr}\")\n                    else:\n                        bad_access_count[request.remote_addr] = 1\n                        log.info(f\"web: failed login attempt, user: {auth.username}, from ip {request.remote_addr}.\")\n                    return authenticate()\n            else:\n                return authenticate()\n        if auth_method == 'token':\n            if 'user' in session and 'token' not in request.args:\n                user = session['user']\n                return f(*args, **kwargs)\n            elif 'token' in request.args:\n                token = request.args.get('token')\n                token_user = var.db.get(\"web_token\", token, fallback=None)\n                if token_user is not None:\n                    user = token_user\n\n                    user_info = var.db.get(\"user\", user, fallback=None)\n                    user_dict = json.loads(user_info)\n                    user_dict['IP'] = request.remote_addr\n                    var.db.set(\"user\", user, json.dumps(user_dict))\n\n                    log.debug(\n                        f\"web: new user access, token validated for the user: {token_user}, from ip {request.remote_addr}.\")\n                    session['token'] = token\n                    session['user'] = token_user\n                    return f(*args, **kwargs)\n\n            if request.remote_addr in bad_access_count:\n                bad_access_count[request.remote_addr] += 1\n                log.info(f\"web: bad token from ip {request.remote_addr}, \"\n                         f\"{bad_access_count[request.remote_addr]} attempts.\")\n                if bad_access_count[request.remote_addr] > var.config.getint(\"webinterface\", \"max_attempts\"):\n                    banned_ip.append(request.remote_addr)\n                    log.info(f\"web: access banned for {request.remote_addr}\")\n            else:\n                bad_access_count[request.remote_addr] = 1\n                log.info(f\"web: bad token from ip {request.remote_addr}.\")\n\n            return render_template(f'need_token.{var.language}.html',\n                                   name=var.config.get('bot', 'username'),\n                                   command=f\"{var.config.get('commands', 'command_symbol')[0]}\"\n                                           f\"{var.config.get('commands', 'requests_webinterface_access')}\")\n\n        return f(*args, **kwargs)\n\n    return decorated\n\n\ndef tag_color(tag):\n    num = hash(tag) % 8\n    if num == 0:\n        return \"primary\"\n    elif num == 1:\n        return \"secondary\"\n    elif num == 2:\n        return \"success\"\n    elif num == 3:\n        return \"danger\"\n    elif num == 4:\n        return \"warning\"\n    elif num == 5:\n        return \"info\"\n    elif num == 6:\n        return \"light\"\n    elif num == 7:\n        return \"dark\"\n\n\ndef build_tags_color_lookup():\n    color_lookup = {}\n    for tag in var.music_db.query_all_tags():\n        color_lookup[tag] = tag_color(tag)\n\n    return color_lookup\n\n\ndef get_all_dirs():\n    dirs = [\".\"]\n    paths = var.music_db.query_all_paths()\n    for path in paths:\n        pos = 0\n        while True:\n            pos = path.find(\"/\", pos + 1)\n            if pos == -1:\n                break\n            folder = path[:pos]\n            if folder not in dirs:\n                dirs.append(folder)\n\n    return dirs\n\n\n@web.route(\"/\", methods=['GET'])\n@requires_auth\ndef index():\n    return open(os.path.join(root_dir, f\"web/templates/index.{var.language}.html\"), \"r\").read()\n\n\n@web.route(\"/playlist\", methods=['GET'])\n@requires_auth\ndef playlist():\n    if len(var.playlist) == 0:\n        return jsonify({\n            'items': [],\n            'current_index': -1,\n            'length': 0,\n            'start_from': 0\n        })\n\n    DEFAULT_DISPLAY_COUNT = 11\n    _from = 0\n    _to = 10\n\n    if 'range_from' in request.args and 'range_to' in request.args:\n        _from = int(request.args['range_from'])\n        _to = int(request.args['range_to'])\n    else:\n        if var.playlist.current_index - int(DEFAULT_DISPLAY_COUNT / 2) > 0:\n            _from = var.playlist.current_index - int(DEFAULT_DISPLAY_COUNT / 2)\n            _to = _from - 1 + DEFAULT_DISPLAY_COUNT\n\n    tags_color_lookup = build_tags_color_lookup()  # TODO: cached this?\n    items = []\n\n    for index, item_wrapper in enumerate(var.playlist[_from: _to + 1]):\n        tag_tuples = []\n        for tag in item_wrapper.item().tags:\n            tag_tuples.append([tag, tags_color_lookup[tag]])\n\n        item: Type[BaseItem] = item_wrapper.item()\n\n        title = item.format_title()\n        artist = \"??\"\n        path = \"\"\n        duration = 0\n        if isinstance(item, FileItem):\n            path = item.path\n            if item.artist:\n                artist = item.artist\n            duration = item.duration\n        elif isinstance(item, URLItem):\n            path = f\" <a href=\\\"{item.url}\\\"><i>{item.url}</i></a>\"\n            duration = item.duration\n        elif isinstance(item, PlaylistURLItem):\n            path = f\" <a href=\\\"{item.url}\\\"><i>{item.url}</i></a>\"\n            artist = f\" <a href=\\\"{item.playlist_url}\\\"><i>{item.playlist_title}</i></a>\"\n            duration = item.duration\n        elif isinstance(item, RadioItem):\n            path = f\" <a href=\\\"{item.url}\\\"><i>{item.url}</i></a>\"\n\n        thumb = \"\"\n        if item.type != 'radio' and item.thumbnail:\n            thumb = f\"data:image/PNG;base64,{item.thumbnail}\"\n        else:\n            thumb = \"static/image/unknown-album.png\"\n\n        items.append({\n            'index': _from + index,\n            'id': item.id,\n            'type': item.display_type(),\n            'path': path,\n            'title': title,\n            'artist': artist,\n            'thumbnail': thumb,\n            'tags': tag_tuples,\n            'duration': duration\n        })\n\n    return jsonify({\n        'items': items,\n        'current_index': var.playlist.current_index,\n        'length': len(var.playlist),\n        'start_from': _from\n    })\n\n\ndef status():\n    if len(var.playlist) > 0:\n        return jsonify({'ver': var.playlist.version,\n                        'current_index': var.playlist.current_index,\n                        'empty': False,\n                        'play': not var.bot.is_pause,\n                        'mode': var.playlist.mode,\n                        'volume': var.bot.volume_helper.plain_volume_set,\n                        'playhead': var.bot.playhead\n                        })\n\n    else:\n        return jsonify({'ver': var.playlist.version,\n                        'current_index': var.playlist.current_index,\n                        'empty': True,\n                        'play': not var.bot.is_pause,\n                        'mode': var.playlist.mode,\n                        'volume': var.bot.volume_helper.plain_volume_set,\n                        'playhead': 0\n                        })\n\n\n@web.route(\"/post\", methods=['POST'])\n@requires_auth\ndef post():\n    global log\n\n    payload = request.get_json() if request.is_json else request.form\n    if payload:\n        log.debug(\"web: Post request from %s: %s\" % (request.remote_addr, str(payload)))\n\n        if 'add_item_at_once' in payload:\n            music_wrapper = get_cached_wrapper_by_id(payload['add_item_at_once'], user)\n            if music_wrapper:\n                var.playlist.insert(var.playlist.current_index + 1, music_wrapper)\n                log.info('web: add to playlist(next): ' + music_wrapper.format_debug_string())\n                if not var.bot.is_pause:\n                    var.bot.interrupt()\n                else:\n                    var.bot.is_pause = False\n            else:\n                abort(404)\n\n        if 'add_item_bottom' in payload:\n            music_wrapper = get_cached_wrapper_by_id(payload['add_item_bottom'], user)\n\n            if music_wrapper:\n                var.playlist.append(music_wrapper)\n                log.info('web: add to playlist(bottom): ' + music_wrapper.format_debug_string())\n            else:\n                abort(404)\n\n        elif 'add_item_next' in payload:\n            music_wrapper = get_cached_wrapper_by_id(payload['add_item_next'], user)\n            if music_wrapper:\n                var.playlist.insert(var.playlist.current_index + 1, music_wrapper)\n                log.info('web: add to playlist(next): ' + music_wrapper.format_debug_string())\n            else:\n                abort(404)\n\n        elif 'add_url' in payload:\n            music_wrapper = get_cached_wrapper_from_scrap(type='url', url=payload['add_url'], user=user)\n            var.playlist.append(music_wrapper)\n\n            log.info(\"web: add to playlist: \" + music_wrapper.format_debug_string())\n            if len(var.playlist) == 2:\n                # If I am the second item on the playlist. (I am the next one!)\n                var.bot.async_download_next()\n\n        elif 'add_radio' in payload:\n            url = payload['add_radio']\n            music_wrapper = get_cached_wrapper_from_scrap(type='radio', url=url, user=user)\n            var.playlist.append(music_wrapper)\n\n            log.info(\"cmd: add to playlist: \" + music_wrapper.format_debug_string())\n\n        elif 'delete_music' in payload:\n            music_wrapper = var.playlist[int(payload['delete_music'])]\n            log.info(\"web: delete from playlist: \" + music_wrapper.format_debug_string())\n\n            if len(var.playlist) >= int(payload['delete_music']):\n                index = int(payload['delete_music'])\n\n                if index == var.playlist.current_index:\n                    var.playlist.remove(index)\n\n                    if index < len(var.playlist):\n                        if not var.bot.is_pause:\n                            var.bot.interrupt()\n                            var.playlist.current_index -= 1\n                            # then the bot will move to next item\n\n                    else:  # if item deleted is the last item of the queue\n                        var.playlist.current_index -= 1\n                        if not var.bot.is_pause:\n                            var.bot.interrupt()\n                else:\n                    var.playlist.remove(index)\n\n        elif 'play_music' in payload:\n            music_wrapper = var.playlist[int(payload['play_music'])]\n            log.info(\"web: jump to: \" + music_wrapper.format_debug_string())\n\n            if len(var.playlist) >= int(payload['play_music']):\n                var.bot.play(int(payload['play_music']))\n                time.sleep(0.1)\n        elif 'move_playhead' in payload:\n            if float(payload['move_playhead']) < var.playlist.current_item().item().duration:\n                log.info(f\"web: move playhead to {float(payload['move_playhead'])} s.\")\n                var.bot.play(var.playlist.current_index, float(payload['move_playhead']))\n\n        elif 'delete_item_from_library' in payload:\n            _id = payload['delete_item_from_library']\n            var.playlist.remove_by_id(_id)\n            item = var.cache.get_item_by_id(_id)\n\n            if os.path.isfile(item.uri()):\n                log.info(\"web: delete file \" + item.uri())\n                os.remove(item.uri())\n\n            var.cache.free_and_delete(_id)\n            time.sleep(0.1)\n\n        elif 'add_tag' in payload:\n            music_wrappers = get_cached_wrappers_by_tags([payload['add_tag']], user)\n            for music_wrapper in music_wrappers:\n                log.info(\"cmd: add to playlist: \" + music_wrapper.format_debug_string())\n            var.playlist.extend(music_wrappers)\n\n        elif 'action' in payload:\n            action = payload['action']\n            if action == \"random\":\n                if var.playlist.mode != \"random\":\n                    var.playlist = media.playlist.get_playlist(\"random\", var.playlist)\n                else:\n                    var.playlist.randomize()\n                var.bot.interrupt()\n                var.db.set('playlist', 'playback_mode', \"random\")\n                log.info(\"web: playback mode changed to random.\")\n            if action == \"one-shot\":\n                var.playlist = media.playlist.get_playlist(\"one-shot\", var.playlist)\n                var.db.set('playlist', 'playback_mode', \"one-shot\")\n                log.info(\"web: playback mode changed to one-shot.\")\n            if action == \"repeat\":\n                var.playlist = media.playlist.get_playlist(\"repeat\", var.playlist)\n                var.db.set('playlist', 'playback_mode', \"repeat\")\n                log.info(\"web: playback mode changed to repeat.\")\n            if action == \"autoplay\":\n                var.playlist = media.playlist.get_playlist(\"autoplay\", var.playlist)\n                var.db.set('playlist', 'playback_mode', \"autoplay\")\n                log.info(\"web: playback mode changed to autoplay.\")\n            if action == \"rescan\":\n                var.cache.build_dir_cache()\n                var.music_db.manage_special_tags()\n                log.info(\"web: Local file cache refreshed.\")\n            elif action == \"stop\":\n                if var.config.getboolean(\"bot\", \"clear_when_stop_in_oneshot\") \\\n                        and var.playlist.mode == 'one-shot':\n                    var.bot.clear()\n                else:\n                    var.bot.stop()\n            elif action == \"next\":\n                if not var.bot.is_pause:\n                    var.bot.interrupt()\n                else:\n                    var.playlist.next()\n                    var.bot.wait_for_ready = True\n            elif action == \"pause\":\n                var.bot.pause()\n            elif action == \"resume\":\n                var.bot.resume()\n            elif action == \"clear\":\n                var.bot.clear()\n            elif action == \"volume_up\":\n                if var.bot.volume_helper.plain_volume_set + 0.03 < 1.0:\n                    var.bot.volume_helper.set_volume(var.bot.volume_helper.plain_volume_set + 0.03)\n                else:\n                    var.bot.volume_helper.set_volume(1.0)\n                var.db.set('bot', 'volume', str(var.bot.volume_helper.plain_volume_set))\n                log.info(\"web: volume up to %d\" % (var.bot.volume_helper.plain_volume_set * 100))\n            elif action == \"volume_down\":\n                if var.bot.volume_helper.plain_volume_set - 0.03 > 0:\n                    var.bot.volume_helper.set_volume(var.bot.unconverted_volume - 0.03)\n                else:\n                    var.bot.volume_helper.set_volume(1.0)\n                var.db.set('bot', 'volume', str(var.bot.volume_helper.plain_volume_set))\n                log.info(\"web: volume down to %d\" % (var.bot.volume_helper.plain_volume_set * 100))\n            elif action == \"volume_set_value\":\n                if 'new_volume' in payload:\n                    if float(payload['new_volume']) > 1:\n                        var.bot.volume_helper.set_volume(1.0)\n                    elif float(payload['new_volume']) < 0:\n                        var.bot.volume_helper.set_volume(0)\n                    else:\n                        # value for new volume is between 0 and 1, round to two decimal digits\n                        var.bot.volume_helper.set_volume(round(float(payload['new_volume']), 2))\n\n                    var.db.set('bot', 'volume', str(var.bot.volume_helper.plain_volume_set))\n                    log.info(\"web: volume set to %d\" % (var.bot.volume_helper.plain_volume_set * 100))\n\n    return status()\n\n\ndef build_library_query_condition(form):\n    try:\n        condition = Condition()\n\n        types = form['type'].split(\",\")\n        sub_cond = Condition()\n        for type in types:\n            sub_cond.or_equal(\"type\", type)\n        condition.and_sub_condition(sub_cond)\n\n        if form['type'] == 'file':\n            folder = form['dir']\n            if folder == \".\":\n                folder = \"\"\n            if not folder.endswith('/') and folder:\n                folder += '/'\n            condition.and_like('path', folder + '%')\n\n        tags = form['tags'].split(\",\")\n        for tag in tags:\n            if tag:\n                condition.and_like(\"tags\", f\"%{tag},%\", case_sensitive=False)\n\n        _keywords = form['keywords'].split(\" \")\n        keywords = []\n        for kw in _keywords:\n            if kw:\n                keywords.append(kw)\n\n        for keyword in keywords:\n            condition.and_like(\"keywords\", f\"%{keyword}%\", case_sensitive=False)\n\n        condition.order_by('create_at', desc=True)\n\n        return condition\n    except KeyError:\n        abort(400)\n\n\n@web.route(\"/library/info\", methods=['GET'])\n@requires_auth\ndef library_info():\n    global log\n\n    while var.cache.dir_lock.locked():\n        time.sleep(0.1)\n\n    tags = var.music_db.query_all_tags()\n    max_upload_file_size = util.parse_file_size(var.config.get(\"webinterface\", \"max_upload_file_size\"))\n\n    return jsonify(dict(\n        dirs=get_all_dirs(),\n        upload_enabled=var.config.getboolean(\"webinterface\", \"upload_enabled\") or var.bot.is_admin(user),\n        delete_allowed=var.config.getboolean(\"bot\", \"delete_allowed\") or var.bot.is_admin(user),\n        tags=tags,\n        max_upload_file_size=max_upload_file_size\n    ))\n\n\n@web.route(\"/library\", methods=['POST'])\n@requires_auth\ndef library():\n    global log\n    ITEM_PER_PAGE = 10\n\n    payload = request.form if request.form else request.json\n    if payload:\n        log.debug(\"web: Post request from %s: %s\" % (request.remote_addr, str(payload)))\n\n        if payload['action'] in ['add', 'query', 'delete']:\n            condition = build_library_query_condition(payload)\n\n            total_count = 0\n            try:\n                total_count = var.music_db.query_music_count(condition)\n            except sqlite3.OperationalError:\n                pass\n\n            if not total_count:\n                return jsonify({\n                    'items': [],\n                    'total_pages': 0,\n                    'active_page': 0\n                })\n\n            if payload['action'] == 'add':\n                items = dicts_to_items(var.music_db.query_music(condition))\n                music_wrappers = []\n                for item in items:\n                    music_wrapper = get_cached_wrapper(item, user)\n                    music_wrappers.append(music_wrapper)\n\n                    log.info(\"cmd: add to playlist: \" + music_wrapper.format_debug_string())\n\n                var.playlist.extend(music_wrappers)\n\n                return redirect(\"./\", code=302)\n            elif payload['action'] == 'delete':\n                if var.config.getboolean(\"bot\", \"delete_allowed\"):\n                    items = dicts_to_items(var.music_db.query_music(condition))\n                    for item in items:\n                        var.playlist.remove_by_id(item.id)\n                        item = var.cache.get_item_by_id(item.id)\n\n                        if os.path.isfile(item.uri()):\n                            log.info(\"web: delete file \" + item.uri())\n                            os.remove(item.uri())\n\n                        var.cache.free_and_delete(item.id)\n\n                    if len(os.listdir(var.music_folder + payload['dir'])) == 0:\n                        os.rmdir(var.music_folder + payload['dir'])\n\n                    time.sleep(0.1)\n                    return redirect(\"./\", code=302)\n                else:\n                    abort(403)\n            else:\n                page_count = math.ceil(total_count / ITEM_PER_PAGE)\n\n                current_page = int(payload['page']) if 'page' in payload else 1\n                if current_page <= page_count:\n                    condition.offset((current_page - 1) * ITEM_PER_PAGE)\n                else:\n                    current_page = 1\n\n                condition.limit(ITEM_PER_PAGE)\n                items = dicts_to_items(var.music_db.query_music(condition))\n\n                results = []\n                for item in items:\n                    result = {'id': item.id, 'title': item.title, 'type': item.display_type(),\n                              'tags': [(tag, tag_color(tag)) for tag in item.tags]}\n                    if item.type != 'radio' and item.thumbnail:\n                        result['thumb'] = f\"data:image/PNG;base64,{item.thumbnail}\"\n                    else:\n                        result['thumb'] = \"static/image/unknown-album.png\"\n\n                    if item.type == 'file':\n                        result['path'] = item.path\n                        result['artist'] = item.artist\n                    else:\n                        result['path'] = item.url\n                        result['artist'] = \"??\"\n\n                    results.append(result)\n\n                return jsonify({\n                    'items': results,\n                    'total_pages': page_count,\n                    'active_page': current_page\n                })\n        elif payload['action'] == 'edit_tags':\n            tags = list(dict.fromkeys(payload['tags'].split(\",\")))  # remove duplicated items\n            if payload['id'] in var.cache:\n                music_wrapper = get_cached_wrapper_by_id(payload['id'], user)\n                music_wrapper.clear_tags()\n                music_wrapper.add_tags(tags)\n                var.playlist.version += 1\n            else:\n                item = var.music_db.query_music_by_id(payload['id'])\n                item['tags'] = tags\n                var.music_db.insert_music(item)\n            return redirect(\"./\", code=302)\n\n    else:\n        abort(400)\n\n\n@web.route('/upload', methods=[\"POST\"])\n@requires_auth\ndef upload():\n    global log\n\n    if not var.config.getboolean(\"webinterface\", \"upload_enabled\"):\n        abort(403)\n\n    file = request.files['file']\n    if not file:\n        abort(400)\n\n    filename = file.filename\n    if filename == '':\n        abort(400)\n\n    targetdir = request.form['targetdir'].strip()\n    if targetdir == '':\n        targetdir = 'uploads/'\n    elif '../' in targetdir:\n        abort(403)\n\n    log.info('web: Uploading file from %s:' % request.remote_addr)\n    log.info('web: - filename: ' + filename)\n    log.info('web: - targetdir: ' + targetdir)\n    log.info('web: - mimetype: ' + file.mimetype)\n\n    if \"audio\" in file.mimetype or \"video\" in file.mimetype:\n        storagepath = os.path.abspath(os.path.join(var.music_folder, targetdir))\n        if not storagepath.startswith(os.path.abspath(var.music_folder)):\n            abort(403)\n\n        try:\n            os.makedirs(storagepath)\n        except OSError as ee:\n            if ee.errno != errno.EEXIST:\n                log.error(f'web: failed to create directory {storagepath}')\n                abort(500)\n\n        filepath = os.path.join(storagepath, filename)\n        log.info('web: - file saved at: ' + filepath)\n        if os.path.exists(filepath):\n            return 'File existed!', 409\n\n        file.save(filepath)\n    else:\n        log.error(f'web: unsupported file type {file.mimetype}! File was not saved.')\n        return 'Unsupported media type!', 415\n\n    return '', 200\n\n\n@web.route('/download', methods=[\"GET\"])\n@requires_auth\ndef download():\n    global log\n\n    if 'id' in request.args and request.args['id']:\n        item = dicts_to_items(var.music_db.query_music(\n            Condition().and_equal('id', request.args['id'])))[0]\n\n        requested_file = item.uri()\n        log.info('web: Download of file %s requested from %s:' % (requested_file, request.remote_addr))\n\n        try:\n            return send_file(requested_file, as_attachment=True)\n        except Exception as e:\n            log.exception(e)\n            abort(404)\n\n    else:\n        condition = build_library_query_condition(request.args)\n        items = dicts_to_items(var.music_db.query_music(condition))\n\n        zipfile = util.zipdir([item.uri() for item in items])\n\n        try:\n            return send_file(zipfile, as_attachment=True)\n        except Exception as e:\n            log.exception(e)\n            abort(404)\n\n    return abort(400)\n\n\nif __name__ == '__main__':\n    web.run(port=8181, host=\"127.0.0.1\")\n"
  },
  {
    "path": "lang/de_DE.json",
    "content": "{\n    \"cli\": {\n        \"added_tags\": \"<b>{song}</b> wurde mit <i>{tags}</i> verschlagwortet.\",\n        \"added_tags_to_all\": \"Alle Lieder in der Playlist wurden mit <i>{tags}</i> verschlagwortet.\",\n        \"admin_help\": \"<h3>Adminbefehle</h3>\\n<b>Bot</b>\\n<ul>\\n<li><b>!<u>k</u>ill </b> - Bot stoppen</li>\\n<li><b>!update </b> - Bot update</li>\\n<li><b>!userban </b> {user}  - Nutzer bannen</li>\\n<li><b>!userunban </b> {user}  - Nutzer entbannen</li>\\n<li><b>!urlbanlist </b>  - Zeige alle gebannten URLs an</li>\\n<li><b>!urlban </b> [{url}]  - Banne {url} (oder das aktuelle Lied, wenn leer) and lösche diese URL aus der Bibliothek.</li>\\n<li><b>!urlunban </b> {url}  - Entbanne {url}</li>\\n<li><b>!rescan </b> {url}  - Erneuere den lokalen Cache der Musikdateien</li>\\n<li><b>!dropdatabase</b> - Lösche die aktuelle Datenbank. Dadurch gehen alle Einstellungen und die Bibliothek verloren.</li>\\n</ul>\\n<b>Webinterface</b>\\n<ul>\\n<li><b>!<u>webuserlist</u></b> - Zeige alle Nutzer, die auf das Webinterface zugreifen dürfen (wenn die Authentifizierung auf 'password' gestellt ist).</li>\\n<li><b>!<u>webuseradd</u> {nick name}</b> - Erlaube {nick name} den Zugriff auf das Webinterface (wenn die Authentifizierung auf 'password' gestellt ist).</li>\\n<li><b>!<u>webuserdel</u> {nick name}</b> - Lösche den Zugriff von {nick name} auf das Webinterface (wenn die Authentifizierung auf 'password' gestellt ist).</li>\\n</ul>\",\n        \"auto_paused\": \"Sende <i>!play</i>, um die Wiedergabe fortzusetzen!\",\n        \"bad_command\": \"<i>{command}</i>: Befehl nicht verfügbar. Sende <i>!help</i>, um dir alle möglichen Befehle anzuzeigen.\",\n        \"bad_parameter\": \"<i>{command}</i>: Ungültiges Argument.\",\n        \"bad_url\": \"URL nicht verfügbar.\",\n        \"cache_refreshed\": \"Cache erneuert!\",\n        \"change_ducking_volume\": \"Lautstärkeabsenkung wurde von {user} auf {volume} gesetzt.\",\n        \"change_max_volume\": \"\",\n        \"change_mode\": \"Wiedergabemodus wurde von {user} auf <i>{mode}</i> gesetzt.\",\n        \"change_volume\": \"Lautstärke wurde von {user} auf {volume} gesetzt.\",\n        \"cleared\": \"Playlist wurde geleert.\",\n        \"cleared_tags\": \"Alle Tags wurden von <b>{song}</b> entfernt.\",\n        \"cleared_tags_from_all\": \"Alle Tags wurden von allen Songs in der Playlist entfernt.\",\n        \"command_disabled\": \"{command}: Befehl deaktiviert!\",\n        \"current_ducking_volume\": \"Aktuelle Lautstärkeabsenkung: {volume}.\",\n        \"current_max_volume\": \"\",\n        \"current_mode\": \"Aktueller Wiedergabemodus: <i>{mode}</i>\",\n        \"current_volume\": \"Aktuelle Lautstärke: {volume}.\",\n        \"database_dropped\": \"Datenbank gelöscht. Alle Einträge wurde gelöscht.\",\n        \"download_in_progress\": \"<b>{item}</b> wird heruntergeladen ...\",\n        \"error_executing_command\": \"{command}: Befehl fehlgeschlagen: {error}.\",\n        \"file\": \"Datei\",\n        \"file_added\": \"{item} wurde hinzugefügt.\",\n        \"file_deleted\": \"{item} wurde aus der Bibliothek gelöscht.\",\n        \"file_item\": \"<b>{artist} - {title}</b> <i>wurde von</i> {user} <i>hinzugefügt.</i> \",\n        \"file_missed\": \"Datei {file} nicht gefunden. Das Element wurde aus der Playlist entfernt.\",\n        \"help\": \"\",\n        \"invalid_index\": \"<i>{index}</i> ist ein ungültiger Index. Sende <i>!queue</i>, um die aktuelle Playlist anzuzeigen.\",\n        \"last_song_on_the_queue\": \"Letztes Lied in der Wiedergabeliste.\",\n        \"max_volume\": \"\",\n        \"multiple_file_added\": \"Mehrere Elemente wurden hinzugefügt:\",\n        \"multiple_file_deleted\": \"Mehrere Elemente wurden aus der Bibliothek gelöscht:\",\n        \"multiple_file_found\": \"Gefunden:\",\n        \"multiple_matches\": \"Datei wurde nicht gefunden! Meintest du:\",\n        \"new_version_found\": \"<h2>Update verfügbar!</h2> Version {new_version} von botamusique ist verfügbar! <hr />\\n<h3>Changelog</h3>\\n{changelog} <hr /> Sende <i>!update</i>, um das Update zu starten!\",\n        \"next_to_play\": \"Nächster Song.\",\n        \"no_file\": \"Datei nicht gefunden.\",\n        \"not_admin\": \"Du bist kein Administrator!\",\n        \"not_in_my_channel\": \"Du bist nicht in meinem Kanal!\",\n        \"not_playing\": \"Aktuell läuft keine Wiedergabe.\",\n        \"now_playing\": \"{item} wird wiedergegeben.\",\n        \"page_instruction\": \"Seite {current}/{total}. Nutze <i>!{command} {{page}}</i>, um zu navigieren.\",\n        \"paused\": \"Wiedergabe pausiert.\",\n        \"playlist_fetching_failed\": \"Playlist konnte nicht geladen werden!\",\n        \"pm_not_allowed\": \"Private Nachrichten sind nicht erlaubt.\",\n        \"position_in_the_queue\": \"Aktuelle Position der Wiedergabeliste: {position}\",\n        \"preconfigurated_radio\": \"Folgende Radiosender wurden vorkonfiguriert und sind verfügbar:\",\n        \"queue_contents\": \"Elemente in der Wiedergabeliste:\",\n        \"queue_empty\": \"Wiedergabeliste ist leer!\",\n        \"radio\": \"Radiosender\",\n        \"radio_item\": \"\",\n        \"rb_play_empty\": \"\",\n        \"rb_query_result\": \"\",\n        \"records_omitted\": \"\",\n        \"removed_tags\": \"\",\n        \"removed_tags_from_all\": \"\",\n        \"removing_item\": \"\",\n        \"repeat\": \"\",\n        \"report_version\": \"\",\n        \"shortlist_instruction\": \"Sende <i>!sl {indexes}</i>, um das gewünscht Element abzuspielen.\",\n        \"start_updating\": \"\",\n        \"stopped\": \"\",\n        \"too_long\": \"\",\n        \"unable_download\": \"\",\n        \"unable_play\": \"\",\n        \"unknown_mode\": \"\",\n        \"update_successful\": \"\",\n        \"url\": \"\",\n        \"url_ban\": \"\",\n        \"url_ban_list\": \"\",\n        \"url_ban_success\": \"\",\n        \"url_from_playlist\": \"\",\n        \"url_from_playlist_item\": \"\",\n        \"url_item\": \"\",\n        \"url_unban_success\": \"\",\n        \"url_unwhitelist_success\": \"\",\n        \"url_whitelist_list\": \"\",\n        \"url_whitelist_success\": \"\",\n        \"user_ban\": \"\",\n        \"user_ban_list\": \"\",\n        \"user_ban_success\": \"\",\n        \"user_password_set\": \"\",\n        \"user_unban_success\": \"\",\n        \"web_user_list\": \"\",\n        \"webpage_address\": \"\",\n        \"which_command\": \"\",\n        \"wrong_pattern\": \"\",\n        \"yt_no_more\": \"\",\n        \"yt_query_error\": \"\",\n        \"yt_result\": \"Ergebnis der YouTube-Suche:\\n{result_table}\\nSende <i>!sl {{indexes}}</i>, um das gewünscht Element abzuspielen.\\n<i>!ytquery -n</i>, um die nächste Seite aufzurufen.\"\n    },\n    \"web\": {\n        \"action\": \"\",\n        \"add\": \"\",\n        \"add_all\": \"\",\n        \"add_radio\": \"\",\n        \"add_radio_url\": \"\",\n        \"add_to_bottom\": \"\",\n        \"add_to_bottom_of_current_playlist\": \"\",\n        \"add_to_playlist_next\": \"\",\n        \"add_url\": \"\",\n        \"add_youtube_or_soundcloud_url\": \"\",\n        \"are_you_really_sure\": \"\",\n        \"aria_botamusique_logo\": \"\",\n        \"aria_default_cover\": \"\",\n        \"aria_empty_box\": \"\",\n        \"aria_remove_this_song\": \"\",\n        \"aria_skip_current_song\": \"\",\n        \"aria_skip_to_next_track\": \"\",\n        \"aria_spinner\": \"\",\n        \"aria_warning_of_deletion\": \"\",\n        \"autoplay\": \"\",\n        \"browse_music_file\": \"\",\n        \"cancel\": \"\",\n        \"cancel_upload_warning\": \"\",\n        \"change_playback_mode\": \"\",\n        \"choose_file\": \"\",\n        \"clear_playlist\": \"\",\n        \"close\": \"\",\n        \"delete_all\": \"\",\n        \"delete_all_files\": \"\",\n        \"delete_file_warning\": \"\",\n        \"directory\": \"\",\n        \"download_all\": \"\",\n        \"download_song_from_library\": \"\",\n        \"edit_submit\": \"\",\n        \"edit_tags_for\": \"\",\n        \"expand_playlist\": \"\",\n        \"file\": \"\",\n        \"filters\": \"\",\n        \"index\": \"#\",\n        \"keywords\": \"\",\n        \"keywords_placeholder\": \"\",\n        \"mini_player_title\": \"\",\n        \"music_library\": \"\",\n        \"next_to_play\": \"\",\n        \"no_tag\": \"\",\n        \"oneshot\": \"\",\n        \"open_volume_controls\": \"\",\n        \"page_title\": \"\",\n        \"pause\": \"\",\n        \"play\": \"\",\n        \"playlist_controls\": \"\",\n        \"radio\": \"\",\n        \"radio_url_placeholder\": \"\",\n        \"random\": \"\",\n        \"remove_song_from_library\": \"\",\n        \"repeat\": \"\",\n        \"rescan_files\": \"\",\n        \"skip_track\": \"\",\n        \"submit\": \"\",\n        \"tags\": \"\",\n        \"tags_to_add\": \"\",\n        \"title\": \"\",\n        \"token\": \"\",\n        \"token_required\": \"\",\n        \"token_required_message\": \"\",\n        \"type\": \"\",\n        \"upload_file\": \"\",\n        \"upload_submit\": \"\",\n        \"upload_to\": \"\",\n        \"uploaded_finished\": \"\",\n        \"uploading_files\": \"\",\n        \"url\": \"\",\n        \"url_path\": \"\",\n        \"url_placeholder\": \"\",\n        \"volume_slider\": \"\"\n    }\n}"
  },
  {
    "path": "lang/en_US.json",
    "content": "{\n    \"cli\": {\n        \"added_tags\": \"Added tags <i>{tags}</i> to <b>{song}</b>.\",\n        \"added_tags_to_all\": \"Added tags <i>{tags}</i> to songs on the playlist.\",\n        \"admin_help\": \"<h3>Admin command</h3>\\n<b>Bot</b>\\n<ul>\\n<li><b>!<u>k</u>ill </b> - kill the bot</li>\\n<li><b>!update </b> - update the bot</li>\\n<li><b>!userban </b> {user}  - ban a user</li>\\n<li><b>!userunban </b> {user}  - unban a user</li>\\n<li><b>!urlbanlist </b>  - list banned url</li>\\n<li><b>!urlban </b> [{url}]  - ban {url} (or current item's url by default) and remove this url from the library.</li>\\n<li><b>!urlunban </b> {url}  - unban {url}</li>\\n<li><b>!rescan </b> {url}  - rebuild local music file cache</li>\\n<li><b>!dropdatabase</b> - clear the entire database, you will lose all settings and music library.</li>\\n</ul>\\n<b>Web Interface</b>\\n<ul>\\n<li><b>!<u>webuserlist</u></b> - list all users that have the permission of accessing the web interface, if auth mode is 'password'.</li>\\n<li><b>!<u>webuseradd</u> {nick name}</b> - grant the user with {nick name} the access to the web interface, if auth mode is 'password'.</li>\\n<li><b>!<u>webuserdel</u> {nick name}</b> - revoke the access to the web interface of {nick name}, if auth mode is 'password'.</li>\\n</ul>\",\n        \"auto_paused\": \"Use <i>!play</i> to resume music!\",\n        \"bad_command\": \"<i>{command}</i>: command not found.\",\n        \"bad_parameter\": \"<i>{command}</i>: invalid parameter.\",\n        \"bad_url\": \"Bad URL requested.\",\n        \"cache_refreshed\": \"Cache refreshed!\",\n        \"change_ducking_volume\": \"Volume on ducking set to {volume} by {user}.\",\n        \"change_max_volume\": \"Max volume set to {max} by {user}\",\n        \"change_mode\": \"Playback mode set to <i>{mode}</i> by {user}.\",\n        \"change_volume\": \"Volume set to {volume} by {user}.\",\n        \"cleared\": \"Playlist emptied.\",\n        \"cleared_tags\": \"Removed all tags from <b>{song}</b>.\",\n        \"cleared_tags_from_all\": \"Removed all tags from songs on the playlist.\",\n        \"command_disabled\": \"{command}: command disabled!\",\n        \"current_ducking_volume\": \"Volume on ducking: {volume}.\",\n        \"current_max_volume\": \"Current max volume: {max}.\",\n        \"current_mode\": \"Current playback mode is <i>{mode}</i>.\",\n        \"current_volume\": \"Current volume: {volume}.\",\n        \"database_dropped\": \"Database dropped. All records have gone.\",\n        \"download_in_progress\": \"Download of <b>{item}</b> in progress...\",\n        \"error_executing_command\": \"{command}: Command failed with error: {error}.\",\n        \"file\": \"File\",\n        \"file_added\": \"Added {item}. \",\n        \"file_deleted\": \"Deleted {item} from the library.\",\n        \"file_item\": \"<b>{artist} - {title}</b> <i>added by</i> {user}\",\n        \"file_missed\": \"Music file '{file}' missed! This item has been removed from the playlist.\",\n        \"help\": \"<h3>Commands</h3>\\n<b>Control</b>\\n<ul>\\n<li> <b>!<u>w</u>eb</b> - get the URL of the web interface, if enabled. </li>\\n<li> <b>!play </b> (or <b>!p</b>) [{num}] [{start_from}] - resume from pausing / start to play (the num-th song is num if given) </li>\\n<li> <b>!<u>pa</u>use </b> - pause </li>\\n<li> <b>!<u>st</u>op </b> - stop playing </li>\\n<li> <b>!<u>sk</u>ip </b> - jump to the next song </li>\\n<li> <b>!<u>la</u>st </b> - jump to the last song </li>\\n<li> <b>!<u>v</u>olume </b> {volume} - get or change the volume (from 0 to 100) </li>\\n<li> <b>!<u>m</u>ode </b> [{mode}] - get or set the playback mode, {mode} should be one of <i>one-shot</i> (remove\\nitem once played), <i>repeat</i> (looping through the playlist), <i>random</i> (randomize the playlist),\\n<i>autoplay</i> (randomly grab something from the music library).</li>\\n<li> <b>!duck </b> on/off - enable or disable ducking function </li>\\n<li> <b>!duckv </b> {volume} - set the volume of the bot when ducking is activated </li>\\n<li> <b>!<u>duckt</u>hres </b> - set the threshold of volume to activate ducking (3000 by default) </li>\\n<li> <b>!<u>o</u>ust </b> - stop playing and go to default channel </li>\\n</ul>\\n<b>Playlist</b>\\n<ul>\\n<li> <b>!<u>n</u>ow </b> (or <b>!np</b>) - display the current song </li>\\n<li> <b>!<u>q</u>ueue </b> - display items in the playlist </li>\\n<li> <b>!<u>t</u>ag </b> {tags} - add all items with tags {tags}, tags separated by \\\",\\\". </li>\\n<li> <b>!file </b>(or <b>!f</b>) {path/folder/keyword} - add a single file to the playlist by its path or keyword in its path. </li>\\n<li> <b>!<u>filem</u>atch </b>(or <b>!fm</b>) {pattern} - add all files that match regex {pattern} </li>\\n<li> <b>!<u>ur</u>l </b> {url} - add Youtube or SoundCloud music </li>\\n<li> <b>!<u>playl</u>ist </b> {url} [{offset}] - add all items in a Youtube or SoundCloud playlist, and start with the {offset}-th item </li>\\n<li> <b>!<u>rad</u>io </b> {url} - append a radio {url} to the playlist </li>\\n<li> <b>!<u>rbq</u>uery </b> {keyword} - query http://www.radio-browser.info for a radio station </li>\\n<li> <b>!<u>rbp</u>lay </b> {id} - play a radio station with {id} (eg. !rbplay 96746) </li>\\n<li> <b>!<u>ys</u>earch </b> {keywords} - query youtube. Use <i>!ysearch -n</i> to turn the page. </li>\\n<li> <b>!<u>yp</u>lay </b> {keywords} - add the first search result of {keywords} into the playlist.</li>\\n<li> <b>!<u>sh</u>ortlist </b> (or <b>!sl</b>) {indexes/*} - add {indexes}-th item (or all items if * is given) on the shortlist. </li>\\n<li> <b>!rm </b> {num} - remove the num-th song on the playlist </li>\\n<li> <b>!<u>rep</u>eat </b> [{num}] - repeat current song {num} (1 by default) times.</li>\\n<li> <b>!<u>ran</u>dom </b> - randomize the playlist.</li>\\n</ul>\\n<b>Music Library</b>\\n<ul>\\n<li> <b>!<u>se</u>arch </b> {keywords} - find item with {keywords} in the music library, keywords separated by space.</li>\\n<li> <b>!<u>li</u>stfile </b> [{pattern}] - display list of available files (whose paths match the regex pattern if {pattern} is given) </li>\\n<li> <b>!<u>addt</u>ag </b> [{index}] {tags} - add {tags} to {index}-th(current song if {index} is omitted) item on the playlist, tags separated by \\\",\\\". </li>\\n<li> <b>!<u>addt</u>ag </b> * {tags} - add {tags} to all items on the playlist. </li>\\n<li> <b>!<u>un</u>tag </b> [{index/*}] {tags}/* - remove {tags}/all tags from {index}-th(current song if {index} is omitted) item on the playlist. </li>\\n<li> <b>!<u>fin</u>dtagged </b> (or <b>!ft</b>) {tags} - find item with {tags} in the music library. </li>\\n<li> <b>!<u>del</u>ete </b> {index} - delete {index}-th item on the shortlist from the music library. </li>\\n</ul>\\n<b>Other</b>\\n<ul>\\n<li> <b>!<u>j</u>oinme {token} </b> - join your own channel with {token}.</li>\\n<li> <b>!<u>password</u> {password} </b> - change your password, used to access the web interface.</li>\\n</ul>\",\n        \"invalid_index\": \"Invalid index <i>{index}</i>. Use <i>!queue</i> to see the playlist.\",\n        \"last_song_on_the_queue\": \"Last one on the queue.\",\n        \"max_volume\": \"Volume exceeds max volume of {max}. Setting volume to max.\",\n        \"multiple_file_added\": \"Multiple items added:\",\n        \"multiple_file_deleted\": \"Multiple items deleted from the library:\",\n        \"multiple_file_found\": \"Found:\",\n        \"multiple_matches\": \"File not found! Possible candidates:\",\n        \"new_version_found\": \"<h2>Update Available!</h2> Version {new_version} of botamusique is available! <hr />\\n<h3>Changelog</h3> {changelog} <hr /> Send <i>!update</i> to update!\",\n        \"next_to_play\": \"Next song.\",\n        \"no_file\": \"File not found.\",\n        \"not_admin\": \"You are not an admin!\",\n        \"not_in_my_channel\": \"You're not in my channel!\",\n        \"not_playing\": \"Nothing is playing right now.\",\n        \"now_playing\": \"Playing {item}\",\n        \"page_instruction\": \"Page {current}/{total}. Use <i>!{command} {{page}}</i> to navigate.\",\n        \"paused\": \"Music paused.\",\n        \"playlist_fetching_failed\": \"Unable to fetch the playlist!\",\n        \"pm_not_allowed\": \"Private message aren't allowed.\",\n        \"position_in_the_queue\": \"Position: {position}\",\n        \"preconfigurated_radio\": \"Preconfigurated Radio available:\",\n        \"queue_contents\": \"Items on the playlist:\",\n        \"queue_empty\": \"Playlist is empty!\",\n        \"radio\": \"Radio\",\n        \"radio_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a> <i>from</i> {name} <i>added by</i> {user}\",\n        \"rb_play_empty\": \"Please specify a radio station ID!\",\n        \"rb_query_result\": \"This is the result of your query, send <i> !rbplay {ID} </i> to play a station:\",\n        \"records_omitted\": \"...\",\n        \"removed_tags\": \"Removed tags <i>{tags}</i> from <b>{song}</b>.\",\n        \"removed_tags_from_all\": \"Removed tags <i>{tags}</i> from songs on the playlist.\",\n        \"removing_item\": \"Removed entry {item} from playlist.\",\n        \"repeat\": \"Repeat {song} for {n} times.\",\n        \"report_version\": \"The current version of botamusique is <b>{version}</b>.\",\n        \"shortlist_instruction\": \"Use <i>!sl {indexes}</i> to play the item you want.\",\n        \"start_updating\": \"Start updating...\",\n        \"stopped\": \"Music stopped.\",\n        \"too_long\": \"<b>{song}</b> is too long ({duration} > {max_duration}), removed from playlist!\",\n        \"unable_download\": \"Unable to download <b>{item}</b>. Removed from the library.\",\n        \"unable_play\": \"Unable to play <b>{item}</b>. Removed from the library.\",\n        \"unknown_mode\": \"Unknown playback mode '{mode}'. It should be one of <i>one-shot</i>, <i>repeat</i>, <i>random</i>.\",\n        \"update_successful\": \"<h2>botamusique v{version} Installed!</h2><hr />\\n<h3>Changelog</h3> {changelog} <hr /> Visit <a href=\\\"https://github.com/azlux/botamusique\\\">our github repo</a> for more details!\",\n        \"url\": \"URL\",\n        \"url_ban\": \"The URL {url} is banned! Removed from playlist!\",\n        \"url_ban_list\": \"List of banned URL: <br>{list}\",\n        \"url_ban_success\": \"The following URL is banned: {url}.\",\n        \"url_from_playlist\": \"URL\",\n        \"url_from_playlist_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a> <i>from playlist</i> <a href=\\\"{playlist_url}\\\">{playlist}</a> <i>added by</i> {user}\",\n        \"url_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a> <i>added by</i> {user}\",\n        \"url_unban_success\": \"The following URL is unbanned: {url}.\",\n        \"url_unwhitelist_success\": \"The following URL is un-whitelisted: {url}.\",\n        \"url_whitelist_list\": \"List of whitelisted URL: <br>{list}\",\n        \"url_whitelist_success\": \"The following URL is whitelisted: {url}.\",\n        \"user_ban\": \"You are banned, not allowed to do that!\",\n        \"user_ban_list\": \"List of banned user: <br>{list}\",\n        \"user_ban_success\": \"User {user} is banned.\",\n        \"user_password_set\": \"Your password has been updated.\",\n        \"user_unban_success\": \"User {user} is unbanned.\",\n        \"web_user_list\": \"Following users have the privilege to access the web interface: <br /> {users}\",\n        \"webpage_address\": \"Your own address to access the web interface is <a href=\\\"{address}\\\">{address}</a>\",\n        \"which_command\": \"Do you mean <br /> {commands}\",\n        \"wrong_pattern\": \"Invalid regex: {error}.\",\n        \"yt_no_more\": \"No more results!\",\n        \"yt_query_error\": \"Unable to query youtube!\",\n        \"yt_result\": \"Youtube query result: {result_table} Use <i>!sl {{indexes}}</i> to play the item you want. <br />\\n<i>!ytquery -n</i> for the next page.\"\n    },\n    \"web\": {\n        \"action\": \"Action\",\n        \"add\": \"Add\",\n        \"add_all\": \"Add All\",\n        \"add_radio\": \"Add Radio\",\n        \"add_radio_url\": \"Add Radio URL\",\n        \"add_to_bottom\": \"Add to bottom\",\n        \"add_to_bottom_of_current_playlist\": \"Add to bottom of current playlist\",\n        \"add_to_playlist_next\": \"Add to playlist right after current song\",\n        \"add_url\": \"Add URL\",\n        \"add_youtube_or_soundcloud_url\": \"Add Youtube or Soundcloud URL\",\n        \"are_you_really_sure\": \"Are you really sure?\",\n        \"aria_botamusique_logo\": \"Botamusique Logo: a fox with two headphones, enjoying the music\",\n        \"aria_default_cover\": \"A black square with two eighth notes beamed together.\",\n        \"aria_empty_box\": \"A drawing of an empty box.\",\n        \"aria_remove_this_song\": \"Remove this song from the current playlist\",\n        \"aria_skip_current_song\": \"Skip current song and play this song right now\",\n        \"aria_skip_to_next_track\": \"Skip to next track\",\n        \"aria_spinner\": \"A loading spinner\",\n        \"aria_warning_of_deletion\": \"Warning about deletion of files.\",\n        \"autoplay\": \"Autoplay\",\n        \"browse_music_file\": \"Browse Music file\",\n        \"cancel\": \"Cancel\",\n        \"cancel_upload_warning\": \"<strong>Are you really sure?</strong> <br /> Click again to abort uploading.\",\n        \"change_playback_mode\": \"Change Playback Mode\",\n        \"choose_file\": \"Choose file\",\n        \"clear_playlist\": \"Clear Playlist\",\n        \"close\": \"Close\",\n        \"delete_all\": \"Delete All\",\n        \"delete_all_files\": \"Delete All Listed Files\",\n        \"delete_file_warning\": \"All files listed here, include files on other pages, will be deleted from your hard-drive.\\n                                Is that what you want?\",\n        \"directory\": \"Directory\",\n        \"download_all\": \"Download All\",\n        \"download_song_from_library\": \"Download song from library\",\n        \"edit_submit\": \"Edit!\",\n        \"edit_tags_for\": \"Edit tags for\",\n        \"expand_playlist\": \"See item <span\\n                                    class=\\\"playlist-expand-item-range\\\"></span> on the playlist.\",\n        \"file\": \"File\",\n        \"filters\": \"Filters\",\n        \"index\": \"#\",\n        \"keywords\": \"Keywords\",\n        \"keywords_placeholder\": \"Keywords...\",\n        \"mini_player_title\": \"Now Playing...\",\n        \"music_library\": \"Music Library\",\n        \"next_to_play\": \"Next to play\",\n        \"no_tag\": \"No tag\",\n        \"oneshot\": \"One-shot\",\n        \"open_volume_controls\": \"Open Volume Controls\",\n        \"page_title\": \"botamusique Web Interface\",\n        \"pause\": \"Pause\",\n        \"play\": \"Play\",\n        \"playlist_controls\": \"Playlist controls\",\n        \"radio\": \"Radio\",\n        \"radio_url_placeholder\": \"Radio URL...\",\n        \"random\": \"Random\",\n        \"remove_song_from_library\": \"Remove song from library\",\n        \"repeat\": \"Repeat\",\n        \"rescan_files\": \"Rescan Files\",\n        \"skip_track\": \"Skip Track\",\n        \"submit\": \"Submit\",\n        \"tags\": \"Tags\",\n        \"tags_to_add\": \"Tags to add\",\n        \"title\": \"Title\",\n        \"token\": \"Token\",\n        \"token_required\": \"Token Required\",\n        \"token_required_message\": \"You are accessing the web interface of {{ name }}.\\nA token is needed to grant you access.<br />\\nPlease send \\\"{{ command }}\\\" to the bot in mumble to acquire one.\",\n        \"type\": \"Type\",\n        \"upload_file\": \"Upload File\",\n        \"upload_submit\": \"Upload!\",\n        \"upload_to\": \"Upload To\",\n        \"uploaded_finished\": \"Uploaded finished!\",\n        \"uploading_files\": \"Uploading files...\",\n        \"url\": \"URL\",\n        \"url_path\": \"Url/Path\",\n        \"url_placeholder\": \"URL...\",\n        \"volume_slider\": \"Volume Slider\"\n    }\n}"
  },
  {
    "path": "lang/es_ES.json",
    "content": "{\n    \"cli\": {\n        \"added_tags\": \"Etiquetas <i>{tags}</i> fueron añadidas a <b>{song}</b>.\",\n        \"added_tags_to_all\": \"Etiquetas <i>{tags}</i> fueron añadidas a las canciones en la lista de reproducción.\",\n        \"admin_help\": \"<h3>Comandos de administrador</h3>\\n<b>Bot</b>\\n<ul>\\n<li><b>!<u>k</u>ill </b> - matar al bot</li>\\n<li><b>!update </b> - actualizar al bot</li>\\n<li><b>!userban </b> {user}  - banear a un usuario</li>\\n<li><b>!userunban </b> {user}  - desbanear a un usuario</li>\\n<li><b>!urlbanlist </b>  - listar url baneadas</li>\\n<li><b>!urlban </b> [{url}]  - banear {url} (o por defecto, la url del ítem actual) y eliminar esta url de la biblioteca.</li>\\n<li><b>!urlunban </b> {url}  - desbanear {url}</li>\\n<li><b>!rescan </b> {url}  - reconstruir caché local de ficheros de música</li>\\n<li><b>!dropdatabase</b> - borrar toda la base de datos. Esto eliminará toda su configuración y su biblioteca musical.</li>\\n</ul>\\n<b>Interfaz Web</b>\\n<ul>\\n<li><b>!<u>webuserlist</u></b> - lista todos los usuarios que tienen permiso de acceder a la interfaz web, si el modo de autenticación es 'contraseña'.</li>\\n<li><b>!<u>webuseradd</u> {nickname}</b> - otorga al usuario con {nickname} acceso a la interfaz web, si el modo de autenticación es 'contraseña'.</li>\\n<li><b>!<u>webuserdel</u> {nickname}</b> - revoca el acceso a la interfaz web para {nickname}, si el modo de autenticación es 'contraseña'.</li>\\n</ul>\",\n        \"auto_paused\": \"Usa <i>!play</i> para continuar la reproducción!\",\n        \"bad_command\": \"<i>{command}</i>: comando no encontrado.\",\n        \"bad_parameter\": \"<i>{command}</i>: parámetro inválido.\",\n        \"bad_url\": \"Se solicitó una URL mal formada. \",\n        \"cache_refreshed\": \"Caché fue actualizada!\",\n        \"change_ducking_volume\": \"Volumen en agache ajustado a {volume} por {user}.\",\n        \"change_max_volume\": \"\",\n        \"change_mode\": \"Modo de reproducción ajustado a <i>{mode}</i> por {user}.\",\n        \"change_volume\": \"Volumen ajustado a {volume} por {user}.\",\n        \"cleared\": \"Lista de reproducción ha sido vaciada.\",\n        \"cleared_tags\": \"Eliminadas todas las etiquetas de <b>{song}</b>.\",\n        \"cleared_tags_from_all\": \"Eliminadas todas las etiquetas de las canciones en la lista de reproducción.\",\n        \"command_disabled\": \"{command}: comando desactivado!\",\n        \"current_ducking_volume\": \"Volumen en agache: {volume}.\",\n        \"current_max_volume\": \"\",\n        \"current_mode\": \"Modo actual de reproducción es <i>{mode}</i>.\",\n        \"current_volume\": \"Volumen actual: {volume}.\",\n        \"database_dropped\": \"Base de datos descartada. Todos los registros se han ido.\",\n        \"download_in_progress\": \"Descarga de <b>{item}</b> en progreso...\",\n        \"error_executing_command\": \"{command}: Comando falló, con el siguiente error: {error}.\",\n        \"file\": \"Fichero\",\n        \"file_added\": \"Añadido {item}.\",\n        \"file_deleted\": \"{item} fue eliminado de la biblioteca.\",\n        \"file_item\": \"<b>{artist} - {title}</b> <i>añadido por</i> {user}\",\n        \"file_missed\": \"Fichero de música '{file}' no encontrado! Este ítem ha sido eliminado de la lista de reproducción.\",\n        \"help\": \"<h3>Comandos</h3>\\n<b>Control</b>\\n<ul>\\n<li> <b>!<u>w</u>eb</b> - obtener la URL de la interfaz web, en caso de estar activada. </li>\\n<li> <b>!play </b> (or <b>!p</b>) [{n}] [{empezar_desde}] - continuar desde pausa / empezar a reproducir (desde la n-ésima canción, si n es introducido) </li>\\n<li> <b>!<u>pa</u>use </b> - pausar </li>\\n<li> <b>!<u>st</u>op </b> - parar la reproducción </li>\\n<li> <b>!<u>sk</u>ip </b> - saltar a la siguiente canción </li>\\n<li> <b>!<u>la</u>st </b> - saltar a la última canción </li>\\n<li> <b>!<u>v</u>olume </b> {volumen} - obtener o cambiar el volumen (de 0 a 100) </li>\\n<li> <b>!<u>m</u>ode </b> [{modo}] - obtener o ajustar el modo de reproducción. {modo} debiera ser o bien <i>one-shot</i> (eliminar el ítem de la lista una vez reproducido), <i>repeat</i> (repetir la lista de reproducción una vez terminada), <i>random</i> (aleatorizar la reproducción), o <i>autoplay</i> (reproducir una muestra aleatoria de canciones de la biblioteca musical).</li>\\n<li> <b>!duck </b> on/off - activar o desactivar funcionalidad de agache </li>\\n<li> <b>!duckv </b> - ajustar el volumen del bot para cuando se está en modo de agache </li>\\n<li> <b>!<u>duckt</u>hres </b> - ajustar el nivel de volumen de habla que activa el agache (3000 por defecto) </li>\\n<li> <b>!<u>o</u>ust </b> - parar la reproducción e ir al canal por defecto del bot </li>\\n</ul>\\n<b>Lista de Reproducción</b>\\n<ul>\\n<li> <b>!<u>n</u>ow </b> (o <b>!np</b>) - mostrar la canción actual </li>\\n<li> <b>!<u>q</u>ueue </b> - mostrar ítems actualmente en la lista de reproducción </li>\\n<li> <b>!<u>t</u>ag </b> {etiquetas} - añadir todos los ítems con etiquetas {etiquetas}. Éstas deben ir separadas por coma (\\\",\\\"). </li>\\n<li> <b>!file </b>(or <b>!f</b>) {ruta/carpeta/palabra clave} - añadir un único fichero a la lista de reproducción a partir de su ruta o una palabra clave en su ruta. </li>\\n<li> <b>!<u>filem</u>atch </b>(o <b>!fm</b>) {patrón} - añade todos los ficheros que calzan con la expresión regular {patrón}. </li>\\n<li> <b>!<u>ur</u>l </b> {url} - añade música de Youtube o de SoundCloud </li>\\n<li> <b>!<u>playl</u>ist </b> {url} [{offset}] - añade todos los ítems en una lista de reproducción de Youtube o de Soundcloud, y empieza desde el primer ítem después del {offset} entregado </li>\\n<li> <b>!<u>rad</u>io </b> {url} - agrega una radio {url} a la lista de reproducción </li>\\n<li> <b>!<u>rbq</u>uery </b> {palabra clave} - envía una query a http://www.radio-browser.info para una estación de radio </li>\\n<li> <b>!<u>rbp</u>lay </b> {id} - reproduce una estación de radio con {id} (por ejemplo, !rbplay 96746) </li>\\n<li> <b>!<u>ys</u>earch </b> {palabras clave} - busca en youtube. Use <i>!ysearch -n</i> para avanzar la página. </li>\\n<li> <b>!<u>yp</u>lay </b> {palabras clave} - añade el primer resultado de la búsqueda de {palabras clave} en Youtube a la lista de reproducción.</li>\\n<li> <b>!<u>sh</u>ortlist </b> (o <b>!sl</b>) {n/*} - añade el {n}-ésimo elemento (o todos los elementos si se entrega *) en la lista corta. </li>\\n<li> <b>!rm </b> {n} - elimina la n-ésima canción en la lista de reproducción </li>\\n<li> <b>!<u>rep</u>eat </b> [{n}] - repite la canción actual {n} veces (1 por defecto).</li>\\n<li> <b>!<u>ran</u>dom </b> - baraja la lista de reproducción.</li>\\n</ul>\\n<b>Biblioteca Musical</b>\\n<ul>\\n<li> <b>!<u>se</u>arch </b> {palabras clave} - encuentra elemento con {palabras clave} en la biblioteca musical. Palabras clave separadas por espacios</li>\\n<li> <b>!<u>li</u>stfile </b> [{patrón}] - muestra la lista de ficheros disponibles (cuyas rutas calzan con la expresión regular {patrón}, si éste es entregado) </li>\\n<li> <b>!<u>addt</u>ag </b> [{n}] {etiquetas} - añade {etiquetas} a la {n}-ésima canción (canción actual si {n} es omitida) en la lista de reproducción. Etiquetas separadas por comas (\\\",\\\"). </li>\\n<li> <b>!<u>addt</u>ag </b> * {etiquetas} - añade {etiquetas} a todos los elementos en la lista de reproducción. </li>\\n<li> <b>!<u>un</u>tag </b> [{n/*}] {etiquetas}/* - elimina {etiquetas}/todas las etiquetas de la {n}-ésima canción (canción actual si {n} es omitida) en la lista de reproducción. </li>\\n<li> <b>!<u>fin</u>dtagged </b> (o <b>!ft</b>) {etiquetas} - encuentra elemento con {etiquetas} en la biblioteca musical. </li>\\n<li> <b>!<u>del</u>ete </b> {n} - elimina {n}-ésimo elemento en la lista corta, de la biblioteca musical. </li>\\n</ul>\\n<b>Otros</b>\\n<ul>\\n<li> <b>!<u>j</u>oinme {token} </b> - unirse a tu propio canal con {token}.</li>\\n<li> <b>!<u>password</u> {contraseña} </b> - cambia la contraseña que usa para acceder a la interfaz web.</li>\\n</ul>\",\n        \"invalid_index\": \"Índice <i>{index}</i> inválido. Use '!queue' para ver la lista de reproducción.\",\n        \"last_song_on_the_queue\": \"Última en la cola.\",\n        \"max_volume\": \"\",\n        \"multiple_file_added\": \"Múltiples elementos añadidos:\",\n        \"multiple_file_deleted\": \"Múltiples elementos fueron eliminados de la biblioteca:\",\n        \"multiple_file_found\": \"Encontrado:\",\n        \"multiple_matches\": \"Fichero no encontrado! Posibles candidatos:\",\n        \"new_version_found\": \"<h2>Actualización disponible!</h2> La versión {new_version} de botamusique está disponible! <hr />\\n<h3>Lista de cambios:</h3> {changelog} <hr /> Envía <i>!update</i> para actualizar este bot!\",\n        \"next_to_play\": \"Siguiente canción.\",\n        \"no_file\": \"Fichero no encontrado.\",\n        \"not_admin\": \"Usted no es un administrador!\",\n        \"not_in_my_channel\": \"Tú no estás en mi canal!\",\n        \"not_playing\": \"Nada se está reproduciendo ahora mismo.\",\n        \"now_playing\": \"Reproduciendo {item}\",\n        \"page_instruction\": \"Página {current}/{total}. Use <i>!{command} {{page}}</i> para navegar.\",\n        \"paused\": \"Música pausada.\",\n        \"playlist_fetching_failed\": \"No fue posible obtener la lista de reproducción!\",\n        \"pm_not_allowed\": \"Mensajes privados no están permitidos.\",\n        \"position_in_the_queue\": \"Posición: {position}\",\n        \"preconfigurated_radio\": \"Radio pre-configurada disponible:\",\n        \"queue_contents\": \"Elementos en la lista de reproducción:\",\n        \"queue_empty\": \"Lista de reproducción está vacía!\",\n        \"radio\": \"Radio\",\n        \"radio_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a> <i>de</i> {name} <i>añadido por</i> {user}\",\n        \"rb_play_empty\": \"Por favor especifique el ID de una estación de radio!\",\n        \"rb_query_result\": \"Este es el resultado de su consulta, envíe <i> !rbplay {ID} </i> para reproducir una estación:\",\n        \"records_omitted\": \"...\",\n        \"removed_tags\": \"Eliminadas las etiquetas <i>{tags}</i> de <b>{song}</b>.\",\n        \"removed_tags_from_all\": \"Eliminadas las etiquetas <i>{tags}</i> de las canciones en la lista de reproducción.\",\n        \"removing_item\": \"Eliminado {item} de la lista de reproducción.\",\n        \"repeat\": \"Repetir {song} {n} veces.\",\n        \"report_version\": \"La versión actual de botamusique es <b>{version}</b>.\",\n        \"shortlist_instruction\": \"Use <i>!sl {índices}</i> para reproducir los elementos que usted desea.\",\n        \"start_updating\": \"Empezando la actualización...\",\n        \"stopped\": \"Música fue detenida.\",\n        \"too_long\": \"<b>{song}</b> es muy larga ({duration} > {max_duration}). Eliminada de la lista de reproducción!\",\n        \"unable_download\": \"No fue posible descargar <b>{item}</b>. Eliminado de la biblioteca.\",\n        \"unable_play\": \"No fue posible reproducir <b>{item}</b>. Eliminado de la biblioteca.\",\n        \"unknown_mode\": \"Modo de reproducción  '{mode}' desconocido. Debiera ser o bien <i>one-shot</i>, <i>repeat</i> o <i>random</i>.\",\n        \"update_successful\": \"<h2>botamusique v{version} instalado!</h2><hr />\\n<h3>Lista de cambios</h3> {changelog} <hr /> Visite <a href=\\\"https://github.com/azlux/botamusique\\\">nuestro repositorio en Github</a> para más detalles!\",\n        \"url\": \"URL\",\n        \"url_ban\": \"URL {url} está baneada! Eliminada de la lista de reproducción!\",\n        \"url_ban_list\": \"\",\n        \"url_ban_success\": \"\",\n        \"url_from_playlist\": \"URL\",\n        \"url_from_playlist_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a> <i>de lista de reproducción</i> <a href=\\\"{playlist_url}\\\">{playlist}</a> <i>añadido por</i> {user}\",\n        \"url_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a> <i>añadido por</i> {user}\",\n        \"url_unban_success\": \"\",\n        \"url_unwhitelist_success\": \"\",\n        \"url_whitelist_list\": \"\",\n        \"url_whitelist_success\": \"\",\n        \"user_ban\": \"Tú estás baneado. No tienes permitido hacer eso!\",\n        \"user_ban_list\": \"\",\n        \"user_ban_success\": \"\",\n        \"user_password_set\": \"Su contraseña ha sido actualizada.\",\n        \"user_unban_success\": \"\",\n        \"web_user_list\": \"Los siguientes usuarios tienen el privilegio de acceder a la interfaz web: <br /> {users}\",\n        \"webpage_address\": \"Tu dirección web para acceder a la interfaz es <a href=\\\"{address}\\\">{address}</a>\",\n        \"which_command\": \"Quieres decir <br /> {commands}\",\n        \"wrong_pattern\": \"Expresión regular inválida: {error}\",\n        \"yt_no_more\": \"No hay más resultados!\",\n        \"yt_query_error\": \"Fue imposible consultar a youtube!\",\n        \"yt_result\": \"Resultado de la consulta a youtube: {result_table} Use <i>!sl {{índices}}</i> para reproducir el elemento que usted desea. <br />\\n<i>!ytquery -n</i> para la siguiente página.\"\n    },\n    \"web\": {\n        \"action\": \"Acción\",\n        \"add\": \"Añadir\",\n        \"add_all\": \"Añadir todas\",\n        \"add_radio\": \"Añadir Radio\",\n        \"add_radio_url\": \"Añadir URL de radio\",\n        \"add_to_bottom\": \"Añadir al final\",\n        \"add_to_bottom_of_current_playlist\": \"Añadir al final de la lista de reproducción actual\",\n        \"add_to_playlist_next\": \"Añadir a la lista de reproducción justo después de la canción actual\",\n        \"add_url\": \"Añadir URL\",\n        \"add_youtube_or_soundcloud_url\": \"Añadir URL de Youtube o de Soundcloud\",\n        \"are_you_really_sure\": \"¿Está usted realmente seguro?\",\n        \"aria_botamusique_logo\": \"El logo de Botamusique: un zorro con dos audífonos, disfrutando de la música\",\n        \"aria_default_cover\": \"Un cuadrado negro, con dos corcheas unidas entre sí.\",\n        \"aria_empty_box\": \"El dibujo de una caja vacía.\",\n        \"aria_remove_this_song\": \"Sacar esta canción de la lista de reproducción actual\",\n        \"aria_skip_current_song\": \"Saltar la canción actual y reproducir esta canción ahora mismo\",\n        \"aria_skip_to_next_track\": \"Saltar a la siguiente canción\",\n        \"aria_spinner\": \"Una curva siguiendo la forma de un círculo, para indicar que el elemento está cargándose todavía.\",\n        \"aria_warning_of_deletion\": \"Advertencia acerca de la eliminación de ficheros.\",\n        \"autoplay\": \"Reproducción automática\",\n        \"browse_music_file\": \"Explorar fichero de música\",\n        \"cancel\": \"Cancelar\",\n        \"cancel_upload_warning\": \"<strong>¿Está realmente seguro?</strong> <br /> Haga click de nuevo para abortar la subida.\",\n        \"change_playback_mode\": \"Cambiar Modo de Reproducción.\",\n        \"choose_file\": \"Elija un fichero\",\n        \"clear_playlist\": \"Vaciar la lista de reproducción\",\n        \"close\": \"Cerrar\",\n        \"delete_all\": \"Borrar todo\",\n        \"delete_all_files\": \"Eliminar todos los ficheros listados\",\n        \"delete_file_warning\": \"Todos los archivos listados aquí, incluyendo ficheros en otras páginas, serán eliminados de su disco duro.\\n                                ¿Es eso lo que usted desea?\",\n        \"directory\": \"Directorio\",\n        \"download_all\": \"Descargar todo\",\n        \"download_song_from_library\": \"Descargar canción desde la biblioteca\",\n        \"edit_submit\": \"Editar!\",\n        \"edit_tags_for\": \"Editar etiquetas para\",\n        \"expand_playlist\": \"Ver elemento <span\\n                                    class=\\\"playlist-expand-item-range\\\"></span> en la lista de reproducción.\",\n        \"file\": \"Fichero\",\n        \"filters\": \"Filtros\",\n        \"index\": \"#\",\n        \"keywords\": \"Palabras clave\",\n        \"keywords_placeholder\": \"Palabras clave...\",\n        \"mini_player_title\": \"Ahora reproduciendo...\",\n        \"music_library\": \"Biblioteca musical\",\n        \"next_to_play\": \"Siguiente canción a reproducir\",\n        \"no_tag\": \"Sin etiquetas\",\n        \"oneshot\": \"One-shot\",\n        \"open_volume_controls\": \"Abrir controles de volumen\",\n        \"page_title\": \"Interfaz web de botamusique\",\n        \"pause\": \"Pausar\",\n        \"play\": \"Reanudar\",\n        \"playlist_controls\": \"Controles de la lista de reproducción\",\n        \"radio\": \"Radio\",\n        \"radio_url_placeholder\": \"URL de radio...\",\n        \"random\": \"Aleatorio\",\n        \"remove_song_from_library\": \"Eliminar canción de la biblioteca\",\n        \"repeat\": \"Repetir\",\n        \"rescan_files\": \"Volver a escanear ficheros\",\n        \"skip_track\": \"Saltar canción\",\n        \"submit\": \"Enviar\",\n        \"tags\": \"Etiquetas\",\n        \"tags_to_add\": \"Etiquetas a añadir\",\n        \"title\": \"Título\",\n        \"token\": \"Token\",\n        \"token_required\": \"Se requiere una token\",\n        \"token_required_message\": \"Tú estás accediendo a la interfaz web de {{ name }}.\\nUna token es necesaria para otorgarte acceso.<br />\\nPor favor, envíe \\\"{{ command }}\\\" al bot en mumble para obtener una.\",\n        \"type\": \"Tipo\",\n        \"upload_file\": \"Subir Fichero\",\n        \"upload_submit\": \"Subir!\",\n        \"upload_to\": \"Subir a\",\n        \"uploaded_finished\": \"Subida terminada!\",\n        \"uploading_files\": \"Subiendo ficheros...\",\n        \"url\": \"URL\",\n        \"url_path\": \"Url/Ruta\",\n        \"url_placeholder\": \"URL...\",\n        \"volume_slider\": \"Control deslizante de volumen\"\n    }\n}"
  },
  {
    "path": "lang/fr_FR.json",
    "content": "{\n    \"cli\": {\n        \"added_tags\": \"Tags <i>{tags}</i> ajoutés à <b>{song}</b>.\",\n        \"added_tags_to_all\": \"Tags <i>{tags}</i> ajoutés aux musiques de la playlist.\",\n        \"admin_help\": \"<h3>Commandes Admin</h3>\\n<b>Bot</b>\\n<ul>\\n<li><b>!<u>k</u>ill </b> - tuer le bot</li>\\n<li><b>!update </b> - update the bot</li>\\n<li><b>!userban </b> {user} - bannir un utilisateur</li>\\n<li><b>!userunban </b> {user} - unban a user</li>\\n<li><b>!urlbanlist </b> - liste url interdite</li>\\n<li><b>!urlban </b> [{url}] - interdire {url} (ou l'url de l'élément courant par défaut) et supprimer cette url de la bibliothèque.</li>\\n<li><b>!urlunban </b> {url} - unban {url}</li>\\n<li><b>!rescan </b> {url} - reconstruction du cache des fichiers musicaux locaux</li>\\n<li><b>!dropdatabase</b> - effacez toute la base de données, vous perdrez tous les paramètres et la bibliothèque musicale.</li>\\n</ul>\\n<b>Interface Web</b>\\n<ul>\\n<li><b>!<u>webuserlist</u></b> - liste de tous les utilisateurs qui ont la permission d'accéder à l'interface web, si le mode d'authentification est 'password'.</li>\\n<li><b>!<u>webuseradd</u> {nick name}</b> - accorder à l'utilisateur avec {nick name} l'accès à l'interface web, si le mode d'authentification est 'password'.</li>\\n<li><b>!<u>webuserdel</u> {nick name}</b> - révoquer l'accès à l'interface web de {nick name}, si le mode d'authentification est 'password'.</li>\\n</ul>\",\n        \"auto_paused\": \"<i>!play</i> pour reprendre la lecture!\",\n        \"bad_command\": \"{{command}}: commande non trouvé.\",\n        \"bad_parameter\": \"{command}: commande invalide.\",\n        \"bad_url\": \"Mauvaise URL demandé\",\n        \"cache_refreshed\": \"Cache actualisé!\",\n        \"change_ducking_volume\": \"Volume sur le ducking réglé sur {volume} par {user}.\",\n        \"change_max_volume\": \"Volume max configuré à {max} par {user}\",\n        \"change_mode\": \"Mode de lecture réglé sur <i>{mode}</i> par {user}.\",\n        \"change_volume\": \"Volume réglé sur {volume} par {user}.\",\n        \"cleared\": \"Playlist vidée.\",\n        \"cleared_tags\": \"Suppression de tous les tag de <b>{song}</b>.\",\n        \"cleared_tags_from_all\": \"Suppression de tous les tags des chansons de la playlist.\",\n        \"command_disabled\": \"{command} : commande désactivée !\",\n        \"current_ducking_volume\": \"Volume de ducking: {volume}.\",\n        \"current_max_volume\": \"Volume max actuel : {max}\",\n        \"current_mode\": \"Le mode de lecture actuel est <i>{mode}</i>.\",\n        \"current_volume\": \"Volume actuel : {volume}.\",\n        \"database_dropped\": \"La base de données a été supprimée. Tous les enregistrements ont disparu.\",\n        \"download_in_progress\": \"Téléchargement de <b>{item}</b> en cours...\",\n        \"error_executing_command\": \"{command} : La commande a échoué avec l'erreur : {error}.\",\n        \"file\": \"Fichier\",\n        \"file_added\": \"{item} ajouté.\",\n        \"file_deleted\": \"{item} supprimé de la bibliothèque.\",\n        \"file_item\": \"<b>{artist} - {title}</b> <i>ajouté par</i> {user}\",\n        \"file_missed\": \"Fichier audio '{file}' introuvable! Cet élément a été supprimé de la playlist.\",\n        \"help\": \"<h3>Commandes</h3>\\n<b>Control</b>\\n<ul>\\n<li> <b>!<u>w</u>eb</b> - obtenir l'URL de l'interface web, si elle est activée. </li>\\n<li> <b>!play </b> (ou <b>!p</b>) [{num}] [{start_from}] - reprise de la pause / début de la lecture (à partir de la n° X s'il est donné) </li>\\n<li> <b>!<u>pa</u>use</b> - pause </li>\\n<li> <b>!<u>st</u>op</b> - arrêtez de jouer </li>\\n<li> <b>!<u>sk</u>ip</b> - passer à la chanson suivante </li>\\n<li> <b>!<u>la</u>st</b> - passer à la dernière chanson </li>\\n<li> <b>!<u>v</u>olume</b> {volume} - obtenir ou modifier le volume (de 0 à 100) </li>\\n<li> <b>!<u>m</u>ode</b> [{mode}] - obtenir ou définir le mode de lecture, {mode} doit être l'un de <i>one-shot</i> (supprimer l'élément une fois joué), <i>repeat</i> (boucle de la liste de lecture), <i>ramdom</i> (liste de lecture aléatoire),\\n<i>autoplay</i> (prendre au hasard dans la bibliothèque musicale).</li>\\n<li> <b>!duck</b> on/off - activer ou désactiver la fonction d'esquive </li>\\n<li> <b>!duckv</b> {volume} - définit le volume du bot lorsque le ducking est activé </li>\\n<li> <b>!<u>duckt</u>hres</b> - définir le seuil de volume pour activer le ducking (3000 par défaut) </li>\\n<li> <b>!<u>o</u>ust</b> - arrêtez de jouer et passez sur le canal par défaut </li>\\n</ul>\\n<b>Playist</b>\\n<ul>\\n<li> <b>!<u>n</u>ow </b> (ou <b>!np</b>) - afficher la chanson actuelle </li>\\n<li> <b>!<u>q</u>ueue </b> - afficher les éléments de la playlist </li>\\n<li> <b>!<u>t</u>ag </b> {balises} - ajouter tous les éléments avec les tags {tags}, les balises séparées par \\\",\\\". </li>\\n<li> <b>!file</b> (ou <b>!f</b>) {chemin/dossier/mot-clé} - ajoute un seul fichier à la playlist par son chemin ou un mot-clé. </li>\\n<li> <b>!<u>filem</u>atch </b>(ou <b>!fm</b>) {pattern} - ajouter tous les fichiers qui correspondent à la regex {pattern} </li>\\n<li> <b>!<u>ur</u>l </b> {url} - ajouter de la musique Youtube ou SoundCloud </li>\\n<li> <b>!<u>playl</u>ist </b> {url} [{offset}] - ajouter tous les éléments d'une liste de lecture Youtube ou SoundCloud, et commencer par le {offset}-ième élément </li>\\n<li> <b>!<u>rad</u>io </b> {url} - ajouter une radio {url} à la playlist </li>\\n<li> <b>!<u>rbq</u>uery </b> {keyword} - interroger http://www.radio-browser.info pour une station de radio </li>\\n<li> <b>!<u>rbp</u>lay </b> {id} - jouer une station de radio avec {id} (ex. !rbplay 96746) </li>\\n<li> <b>!<u>ys</u>earch </b> {keywords} - requête youtube. Utilisez <i>!ysearch -n</i> pour aller à la page d'après. </li>\\n<li> <b>!<u>yp</u>lay </b> {keywords} - ajouter le premier résultat de recherche de {keyword} dans la playlist.</li>\\n<li> <b>!<u>sh</u>ortlist </b> (ou <b>!sl</b>) {index/*} - ajouter {index}-ième élément (ou tous les éléments si * est donné) de la liste. </li>\\n<li> <b>!rm </b> {num} - supprimer le num-ième morceau de la playlist </li>\\n<li> <b>!<u>rep</u>eat </b> [{num}] - répéter la chanson actuelle {num} (1 par défaut) times.</li>\\n<li> <b>!<u>ran</u>dom </b> - randomiser la playlist.</li>\\n</ul>\\n<b>Bibliothèque musicale</b>\\n<ul>\\n<li> <b>!<u>se</u>arch </b> {keywords} - trouver un élément avec {mots-clés} dans la bibliothèque musicale, mots-clés séparés par un espace.</li>\\n<li> <b>!<u>li</u>stfile </b> [{pattern}] - affiche la liste des fichiers disponibles (dont les chemins correspondent au motif de regex si {pattern} est donné) </li>\\n<li> <b>!<u>addt</u>ag </b> [{index}] {tags} - ajouter {tags} à {index} (current song if {index} n'existe pas) de la playliste, tags séparer par \\\",\\\". </li>\\n<li> <b>!<u>addt</u>ag </b> * {tags} - ajouter des {tags} à tous les éléments de la playlist. </li>\\n<li> <b>!<u>un</u>tag </b> [{index/*}] {tags}/* - supprimer {tags}/toutes les tags de {index}-th(current song if {index} is oitted) item on the playlist. </li>\\n<li> <b>!<u>fin</u>dtagged </b> (ou <b>!ft</b>) {tags} - trouver un élément avec des {balises} dans la bibliothèque. </li>\\n<li> <b>!<u>del</u>ete </b> {index} - supprimer le {index}-ième élément de la liste de la bibliothèque. </li>\\n</ul>\\n<b>Autre</b>\\n<ul>\\n<li> <b>!<u>j</u>oinme {token} </b> - rejoins votre propre channel mumble avec {token}.</li>\\n<li> <b>!<u>password</u> {password} </b> - changer votre mot de passe, utilisé pour accéder à l'interface web.</li>\\n</ul>\",\n        \"invalid_index\": \"Index non valide <i>{index}</i>. Utilisez '!queue' pour voir la playlist.\",\n        \"last_song_on_the_queue\": \"Dernier de la file d'attente.\",\n        \"max_volume\": \"Le volume dépasse le maximum {max}. Réglage du volume sur le max.\",\n        \"multiple_file_added\": \"Ajout de plusieurs éléments :\",\n        \"multiple_file_deleted\": \"Plusieurs éléments ont été supprimés de la bibliothèque :\",\n        \"multiple_file_found\": \"Trouvé :\",\n        \"multiple_matches\": \"Fichier non trouvé ! Candidats possibles :\",\n        \"new_version_found\": \"<h2>Mise à jour disponible!</h2> La version {new_version} de botamusique est disponible ! <hr />\\n<h3>Changelog</h3> {changelog} <hr /> Envoyer <i>!update</i> pour mettre à jour !\",\n        \"next_to_play\": \"Chanson suivante.\",\n        \"no_file\": \"Fichier non trouvé.\",\n        \"not_admin\": \"Vous n'êtes pas un admin !\",\n        \"not_in_my_channel\": \"Vous n'êtes pas dans mon canal, commande refusé !\",\n        \"not_playing\": \"Rien n'est joué en ce moment.\",\n        \"now_playing\": \"En cours de lecture {item}\",\n        \"page_instruction\": \"Page {current}/{total}. Utilisez <i>!{command} {{page}}</i> pour naviguer.\",\n        \"paused\": \"Music en pause.\",\n        \"playlist_fetching_failed\": \"Impossible d'obtenir la playlist !\",\n        \"pm_not_allowed\": \"Les messages privés ne sont pas autorisés.\",\n        \"position_in_the_queue\": \"Position: {position}\",\n        \"preconfigurated_radio\": \"Radio préconfigurées disponible :\",\n        \"queue_contents\": \"Éléments de la playlist :\",\n        \"queue_empty\": \"La playlist est vide !\",\n        \"radio\": \"Radio\",\n        \"radio_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a> <i>from</i> {name} <i>ajouté par</i> {user}\",\n        \"rb_play_empty\": \"Veuillez préciser l'ID de la station de radio !\",\n        \"rb_query_result\": \"Résultat de votre requête, envoyez !rbplay 'ID' pour jouer une station :\",\n        \"records_omitted\": \"...\",\n        \"removed_tags\": \"Suppression des tags <i>{tags}</i> de <b>{song}</b>.\",\n        \"removed_tags_from_all\": \"Suppression des tags <i>{tags}</i> des chansons de la playlist.\",\n        \"removing_item\": \"Entrée {item} suprimée de la playlist.\",\n        \"repeat\": \"Répète {song} {n} fois.\",\n        \"report_version\": \"La version actuelle de botamusique est <b>{version}{/b}.\",\n        \"shortlist_instruction\": \"Utilisez <i>!sl {indexes}</i> pour jouer l'élément que vous voulez.\",\n        \"start_updating\": \"Début de la mise à jour...\",\n        \"stopped\": \"Musique arrêté.\",\n        \"too_long\": \"<b>{song}</b> est trop long ({duration} > {max_duration}), supprimé de la playlist !\",\n        \"unable_download\": \"Impossible de télécharger <b>{item}</b>. Retiré de la bibliothèque.\",\n        \"unable_play\": \"Impossible de jouer <b>{item}</b>. Retiré de la bibliothèque.\",\n        \"unknown_mode\": \"Mode de lecture \\\"{mode}\\\" inconnu. Il devrait s'agir d'un des modes suivants : <i>one-shot</i>, <i>repeat</i>, <i>random</i>.\",\n        \"update_successful\": \"<h2>botamusique v{version} Installé ! </h2><hr />\\n<h3>Changelog</h3> {changelog} <hr /> Visitez <a href=\\\"https://github.com/azlux/botamusique\\\">notre repo github</a> pour plus de détails !\",\n        \"url\": \"URL\",\n        \"url_ban\": \"URL {url} est interdite !\",\n        \"url_ban_list\": \"Liste des URL bannies:<br>{list=\",\n        \"url_ban_success\": \"L'URL suivante est interdite: {url}\",\n        \"url_from_playlist\": \"URL\",\n        \"url_from_playlist_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a> <i>depuis la playlist</i> <a href=\\\"{playlist_url}\\\">{playlist}</a> <i>ajouté par</i> {user}\",\n        \"url_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a> <i>ajouté par</i> {user}\",\n        \"url_unban_success\": \"L'URL suivante est débloquée : {url}.\",\n        \"url_unwhitelist_success\": \"L'URL suivante n'est pas sur liste blanche : {url}.\",\n        \"url_whitelist_list\": \"Liste des URL sur liste blanche: <br>{list}\",\n        \"url_whitelist_success\": \"L'URL suivante est sur la liste blanche : {url}.\",\n        \"user_ban\": \"Vous êtes banni, vous n'avez donc pas le droit de faire cela !\",\n        \"user_ban_list\": \"Liste des utilisateurs bannis:<br>{list}\",\n        \"user_ban_success\": \"L'utilisateur {user} est banni.\",\n        \"user_password_set\": \"Votre mot de passe a été mis à jour.\",\n        \"user_unban_success\": \"L'utilisateur {user} n'est plus banni.\",\n        \"web_user_list\": \"Les utilisateurs suivants ont l'autorisation d'accéder à l'interface web : <br /> {users}\",\n        \"webpage_address\": \"Votre propre adresse pour accéder à l'interface web est <a href=\\\"{address}\\\">{address}</a>\",\n        \"which_command\": \"Voulez-vous dire <br /> {commands}\",\n        \"wrong_pattern\": \"regex invalide: {error}.\",\n        \"yt_no_more\": \"Plus de résultats !\",\n        \"yt_query_error\": \"Impossible d'interroger youtube !\",\n        \"yt_result\": \"Résultat de la requête Youtube : {result_table} Utilisez <i>!sl {{indexes}}</i> pour jouer l'entrée que vous voulez. <br />\\n<i>!ytquery -n</i> pour la page suivante.\"\n    },\n    \"web\": {\n        \"action\": \"Action\",\n        \"add\": \"Ajouter\",\n        \"add_all\": \"Ajouter tout\",\n        \"add_radio\": \"Ajouter une Radio\",\n        \"add_radio_url\": \"Ajouter l'URL d'une Radio\",\n        \"add_to_bottom\": \"Ajouter à la fin\",\n        \"add_to_bottom_of_current_playlist\": \"Ajouter à la fin de la playlist actuelle\",\n        \"add_to_playlist_next\": \"Ajouter à la playlist juste après la chanson en cours\",\n        \"add_url\": \"Ajouter l'URL\",\n        \"add_youtube_or_soundcloud_url\": \"Ajouter une URL Youtube ou Soundcloud\",\n        \"are_you_really_sure\": \"En êtes-vous vraiment sûr ?\",\n        \"aria_botamusique_logo\": \"Logo Botamusique : un renard avec deux écouteurs, appréciant la musique\",\n        \"aria_default_cover\": \"Un carré noir avec deux croches qui se rejoignent.\",\n        \"aria_empty_box\": \"Un dessin d'une boîte vide.\",\n        \"aria_remove_this_song\": \"Supprimer cette chanson de la playlist actuelle\",\n        \"aria_skip_current_song\": \"Passer la chanson actuelle et jouer cette chanson maintenant\",\n        \"aria_skip_to_next_track\": \"Passer à la piste suivante\",\n        \"aria_spinner\": \"Une roue de chargement\",\n        \"aria_warning_of_deletion\": \"Avertissement concernant la suppression de fichiers.\",\n        \"autoplay\": \"Autoplay\",\n        \"browse_music_file\": \"Parcourir le dossier de musique\",\n        \"cancel\": \"Annuler\",\n        \"cancel_upload_warning\": \"<strong>Etes-vous vraiment sûr ?</strong> <br />Cliquez à nouveau pour interrompre le téléchargement.\",\n        \"change_playback_mode\": \"Changer de mode de lecture\",\n        \"choose_file\": \"Choisissez un fichier\",\n        \"clear_playlist\": \"Vider la playlist\",\n        \"close\": \"Fermer\",\n        \"delete_all\": \"Supprimer tous\",\n        \"delete_all_files\": \"Supprimer tous les fichiers répertoriés\",\n        \"delete_file_warning\": \"Tous les fichiers énumérés ici, y compris les fichiers des autres pages, seront supprimés de votre disque dur.\\n                                C'est ce que vous voulez ?\",\n        \"directory\": \"Répertoire\",\n        \"download_all\": \"Télécharger tout\",\n        \"download_song_from_library\": \"Télécharger une chanson de la bibliothèque\",\n        \"edit_submit\": \"Editer !\",\n        \"edit_tags_for\": \"Modifier les tags pour\",\n        \"expand_playlist\": \"Voir le point <span\\n                                      class=\\\"playlist-expand-item-range\\\"></span> sur la playlist.\",\n        \"file\": \"Dossier\",\n        \"filters\": \"Filtres\",\n        \"index\": \"#\",\n        \"keywords\": \"Mots-clés\",\n        \"keywords_placeholder\": \"Mots-clés...\",\n        \"mini_player_title\": \"En train de jouer...\",\n        \"music_library\": \"Bibliothèque musicale\",\n        \"next_to_play\": \"Suivant à jouer\",\n        \"no_tag\": \"Pas de tag\",\n        \"oneshot\": \"One-shot\",\n        \"open_volume_controls\": \"Ouvrir le contrôle de volume\",\n        \"page_title\": \"Interface Web botamusique\",\n        \"pause\": \"Pause\",\n        \"play\": \"Jouer\",\n        \"playlist_controls\": \"Contrôle des playlists\",\n        \"radio\": \"Radio\",\n        \"radio_url_placeholder\": \"URL de la radio...\",\n        \"random\": \"Aléatoire\",\n        \"remove_song_from_library\": \"Retirer une chanson de la bibliothèque\",\n        \"repeat\": \"Répéter\",\n        \"rescan_files\": \"Re-scanner les fichiers\",\n        \"skip_track\": \"Passer la piste\",\n        \"submit\": \"Envoyer\",\n        \"tags\": \"Tags\",\n        \"tags_to_add\": \"Tags à ajouter\",\n        \"title\": \"Titre\",\n        \"token\": \"Token\",\n        \"token_required\": \"Token requis\",\n        \"token_required_message\": \"Vous accédez à l'interface web de {{ name }}.\\nUn jeton est nécessaire pour vous permettre d'y accéder.<br />\\nVeuillez envoyer \\\"{{ command }}\\\" au bot sur mumble pour en acquérir un.\",\n        \"type\": \"Type\",\n        \"upload_file\": \"Télécharger un fichier\",\n        \"upload_submit\": \"Téléchargez !\",\n        \"upload_to\": \"Télécharger vers\",\n        \"uploaded_finished\": \"Téléchargement terminé !\",\n        \"uploading_files\": \"Téléchargement de fichiers...\",\n        \"url\": \"URL\",\n        \"url_path\": \"Url/Path\",\n        \"url_placeholder\": \"URL...\",\n        \"volume_slider\": \"Curseur de volume\"\n    }\n}"
  },
  {
    "path": "lang/it_IT.json",
    "content": "{\n    \"cli\": {\n        \"added_tags\": \"Tag <i>{tags}</i> aggiunti a <b>{song}</b>.\",\n        \"added_tags_to_all\": \"I tag <i>{tags}</i> sono stati aggiunti ai brani nella playlist.\",\n        \"admin_help\": \"<h3>Comandi amministratore</h3>\\n<b>Bot</b>\\n<ul>\\n<li><b>!<u>k</u>ill </b> - Termina il bot.</li>\\n<li><b>!update </b> - Aggiorna il bot.</li>\\n<li><b>!userban </b> {user}  - Banna utente.</li>\\n<li><b>!userunban </b> {user}  - Sbanna utente.</li>\\n<li><b>!urlbanlist </b>  - Elenco URL vietati.</li>\\n<li><b>!urlban </b> [{url}]  - Banna {url} (o URL dell'elemento corrente come impostazione predefinita) e rimuovi questo URL dalla libreria.</li>\\n<li><b>!urlunban </b> {url}  - Sbanna {url}.</li>\\n<li><b>!rescan </b> {url}  - Ricostruisce la cache dei file musicali locali.</li>\\n<li><b>!dropdatabase</b> - Cancella l'intero database, perderai tutte le impostazioni e la libreria musicale.</li>\\n</ul>\\n<b>Interfaccia Web</b>\\n<ul>\\n<li><b>!<u>webuserlist</u></b> - Elenca tutti gli utenti che hanno il permesso di accedere all'interfaccia web, se la modalità di autenticazione è 'password'.</li>\\n<li><b>!<u>webuseradd</u> {nick name}</b> - Concedi all'utente con {nick name} l'accesso all'interfaccia web, se la modalità di autenticazione è 'password'.</li>\\n<li><b>!<u>webuserdel</u> {nick name}</b> - Revoca l'accesso all'interfaccia web di {nick name}, se la modalità di autenticazione è 'password'.</li>\\n</ul>\\\"\",\n        \"auto_paused\": \"Usa <i>!play</i> per riprendere la musica!\",\n        \"bad_command\": \"<i>{command}</i>: comando non trovato.\",\n        \"bad_parameter\": \"<i>{command}</i>: parametro non valido.\",\n        \"bad_url\": \"È stato richiesto un URL non valido.\",\n        \"cache_refreshed\": \"Cache aggiornata!\",\n        \"change_ducking_volume\": \"Volume del ducking impostato a {volume} da {user}.\",\n        \"change_max_volume\": \"\",\n        \"change_mode\": \"Modalità di riproduzione impostata su <i>{mode}</i> da {user}.\",\n        \"change_volume\": \"Volume impostato a {volume} da {user}.\",\n        \"cleared\": \"Playlist svuotata.\",\n        \"cleared_tags\": \"Rimossi tutti i tag da <b>{song}</b>.\",\n        \"cleared_tags_from_all\": \"Rimossi tutti i tag dai brani nella playlist.\",\n        \"command_disabled\": \"{command}: comando disabilitato!\",\n        \"current_ducking_volume\": \"Volume ducking attuale: {volume}.\",\n        \"current_max_volume\": \"\",\n        \"current_mode\": \"Modalità di riproduzione corrente: <i>{mode}</i>.\",\n        \"current_volume\": \"Volume attuale: {volume}.\",\n        \"database_dropped\": \"Database eliminato. Tutti i dati sono andati.\",\n        \"download_in_progress\": \"Scaricamento di <b>{item}</b> in corso...\",\n        \"error_executing_command\": \"{command}: Comando non riuscito con errore: {error}.\",\n        \"file\": \"File\",\n        \"file_added\": \"{item} aggiunto.\",\n        \"file_deleted\": \"{item} eliminato dalla libreria.\",\n        \"file_item\": \"<b>{artist} - {title}</b> <i>aggiunto da</i> {user}\",\n        \"file_missed\": \"File musicale \\\"{file}\\\" mancante! Questo elemento è stato rimosso dalla playlist.\",\n        \"help\": \"<h3>Comandi</h3>\\n<b>Controllo</b>\\n<ul>\\n<li><b>!<u>w</u>eb</b> - ottenere l'URL dell'interfaccia web, se abilitata.</li>\\n<li><b>!play</b> (or <b>!p</b>) [{num}] [{start_from}] - Riprende dalla pausa / avvia la riproduzione (dal numero {num} se fornito).</li>\\n<li><b>!<u>pa</u>use</b> - Pausa.</li>\\n<li><b>!<u>st</u>op</b> - Arresta riproduzione.</li>\\n<li><b>!<u>sk</u>ip</b> - Passa al brano successivo.</li>\\n<li><b>!<u>la</u>st</b> - Passa all'ultimo brano.</li>\\n<li><b>!<u>v</u>olume</b> {volume} - Ottenere o modificare il volume (da 0 a 100).</li>\\n<li><b>!<u>m</u>ode</b> [{mode}] - Ottenere o impostare la modalità di riproduzione, {mode} dovrebbe essere <i>one-shot</i> (rimuove l'elemento una volta riprodotto), <i>repeat</i> (ripete la playlist dopo il completamento), <i>random</i> (riproduzione casuale della playlist), <i>autoplay</i> (riproduce brani casuali dalla libreria musicale).</li>\\n<li><b>!duck</b> on/off - Abilitare o disabilitare la funzione ducking.</li>\\n<li><b>!duckv</b> {volume} - Imposta il volume del bot quando il ducking è attivato.</li>\\n<li><b>!<u>duckt</u>hres </b> - Imposta la soglia del volume per attivare il ducking (3000 per impostazione predefinita).</li>\\n<li><b>!<u>o</u>ust</b> - Interrompe la riproduzione e vai al canale predefinito.</li>\\n</ul>\\n<b>Playlist</b>\\n<ul>\\n<li><b>!<u>n</u>ow</b> (or <b>!np</b>) - Visualizza il brano corrente.</li>\\n<li><b>!<u>q</u>ueue</b> - Visualizza gli elementi nella playlist.</li>\\n<li><b>!<u>t</u>ag</b> {tags} - Aggiungi tutti gli elementi con i tag {tags}, tag separati da \\\",\\\".</li>\\n<li><b>!file </b>(or <b>!f</b>) {path/folder/keyword} - Aggiungi un singolo file alla playlist tramite il percorso o la parola chiave nel percorso.</li>\\n<li><b>!<u>filem</u>atch </b>(or <b>!fm</b>) {pattern} - Aggiungi tutti i file che corrispondono all'espressione regolare {pattern}.</li>\\n<li><b>!<u>ur</u>l</b> {url} - Aggiungi musica da YouTube o SoundCloud.</li>\\n<li><b>!<u>playl</u>ist</b> {url} [{offset}] - Aggiungi tutti gli elementi da una playlist di YouTube o SoundCloud e inizia con l'elemento {offset}.</li>\\n<li><b>!<u>rad</u>io</b> {url} - Aggiungi una radio {url} alla playlist.</li>\\n<li><b>!<u>rbq</u>uery</b> {keyword} - Interroga http://www.radio-browser.info per una stazione radio.</li>\\n<li><b>!<u>rbp</u>lay</b> {id} - Riproduce una stazione radio con {id} (es. !rbplay 96746).</li>\\n<li><b>!<u>ys</u>earch</b> {keywords} - Interroga YouTube. Usa <i>!ysearch -n</i> per andare alla pagina successiva.</li>\\n<li><b>!<u>yp</u>lay</b> {keywords} - Aggiungi il primo risultato di ricerca per {keyword} alla playlist.</li>\\n<li><b>!<u>sh</u>ortlist</b> (or <b>!sl</b>) {indexes/*} - Aggiungi {index}-esimo elemento (o tutti gli elementi se * è dato) alla lista.</li>\\n<li><b>!rm</b> {num} - Rimuove il brano {num} dalla playlist.</li>\\n<li><b>!<u>rep</u>eat</b> [{num}] - Ripete il brano corrente {num} volte (1 per impostazione predefinita).</li>\\n<li><b>!<u>ran</u>dom</b> - Playlist in riproduzione casuale.</li>\\n</ul>\\n<b>Libreria Musicale</b>\\n<ul>\\n<li><b>!<u>se</u>arch</b> {keywords} - Trova l'elemento con {keywords} nella libreria musicale, parole chiave separate da spazio.</li>\\n<li><b>!<u>li</u>stfile</b> [{pattern}] - Mostra l'elenco dei file disponibili (i cui percorsi corrispondono all'espressione regolare {pattern}, se fornito).</li>\\n<li><b>!<u>addt</u>ag</b> [{index}] {tags} - Aggiunge {tag} a {index} (brano corrente se {index} è omesso) della playlist, tag separati da \\\",\\\".</li>\\n<li><b>!<u>addt</u>ag</b> * {tags} - Aggiunge {tags} a tutti gli elementi sulla playlist.</li>\\n<li><b>!<u>un</u>tag</b> [{index/*}] {tags}/* - Rimuove {tags}/tutti i tag dall'elemento {index} (brano corrente se {index} è omesso) nella playlist.</li>\\n<li><b>!<u>fin</u>dtagged </b> (or <b>!ft</b>) {tags} - Trova l'elemento con {tags} nella libreria musicale.</li>\\n<li><b>!<u>del</u>ete</b> {index} - Rimuove {index} elemento dall'elenco della libreria musicale.</li>\\n</ul>\\n<b>Altro</b>\\n<ul>\\n<li><b>!<u>j</u>oinme {token}</b> - Unisciti al tuo canale Mumble con {token}.</li>\\n<li><b>!<u>password</u> {password}</b> - Cambia la password, utilizzata per accedere all'interfaccia web.</li>\\n</ul>\\\",\",\n        \"invalid_index\": \"Indice <i>{index}</i> non valido. Usa <i>!queue</i> per vedere la playlist.\",\n        \"last_song_on_the_queue\": \"Ultimo in coda.\",\n        \"max_volume\": \"\",\n        \"multiple_file_added\": \"Più elementi aggiunti:\",\n        \"multiple_file_deleted\": \"Più elementi eliminati dalla libreria:\",\n        \"multiple_file_found\": \"Trovati:\",\n        \"multiple_matches\": \"File non trovato! Possibili candidati:\",\n        \"new_version_found\": \"<h2>Aggiornamento disponibile!</h2> Versione {new_version} di botamusique trovata! <hr />\\\\n<h3>Changelog</h3> {changelog} <hr /> Invia <i>!update</i> per aggiornare!\",\n        \"next_to_play\": \"Brano successivo.\",\n        \"no_file\": \"File non trovato.\",\n        \"not_admin\": \"Non sei un amministratore!\",\n        \"not_in_my_channel\": \"Non sei nel mio canale!\",\n        \"not_playing\": \"Niente in riproduzione in questo momento.\",\n        \"now_playing\": \"{item} in riproduzione\",\n        \"page_instruction\": \"Pagina {corrente}/{totale}. Usa <i>!{command} {{page}}</i> per navigare.\",\n        \"paused\": \"Musica in pausa.\",\n        \"playlist_fetching_failed\": \"Impossibile recuperare la playlist!\",\n        \"pm_not_allowed\": \"Messaggi privati non consentiti.\",\n        \"position_in_the_queue\": \"Posizione: {position}\",\n        \"preconfigurated_radio\": \"Radio preconfigurate disponibili:\",\n        \"queue_contents\": \"Elementi nella playlist:\",\n        \"queue_empty\": \"La playlist è vuota!\",\n        \"radio\": \"Radio\",\n        \"radio_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a> <i>di</i> {name} <i>aggiunto da</i> {user}\",\n        \"rb_play_empty\": \"Si prega di specificare l'ID di una stazione radio!\",\n        \"rb_query_result\": \"Questo è il risultato della tua ricerca, invia <i>!rbplay {ID}</i> per riprodurre una stazione:\",\n        \"records_omitted\": \"...\",\n        \"removed_tags\": \"Tag <i>{tags}</i> rimossi da <b>{song}</b>.\",\n        \"removed_tags_from_all\": \"Tag <i>{tags}</i> rimossi dai brani nella playlist.\",\n        \"removing_item\": \"Voce {item} rimossa dalla playlist.\",\n        \"repeat\": \"Ripeti {song} per {n} volte.\",\n        \"report_version\": \"La versione attuale di Botamusique è <b>{version}</b>.\",\n        \"shortlist_instruction\": \"Usa <i>!sl {indexes}</i> per riprodurre l'elemento desiderato.\",\n        \"start_updating\": \"Inizio aggiornamento...\",\n        \"stopped\": \"Riproduzione interrotta.\",\n        \"too_long\": \"<b>{song}</b> è troppo lunga ({duration} > {max_duration}), rimossa dalla playlist!\",\n        \"unable_download\": \"Impossibile scaricare <b>{item}</b>. Rimosso dalla libreria.\",\n        \"unable_play\": \"Impossibile riprodurre <b>{item}</b>. Rimosso dalla libreria.\",\n        \"unknown_mode\": \"Modalità di riproduzione '{mode}' sconosciuta. Dovrebbe essere <i>one-shot</i>, <i>ripeti</i>, <i>casuale</i>.\",\n        \"update_successful\": \"<h2>botamusique v{version} installato!</h2><hr />\\n<h3>Changelog</h3> {changelog} <hr /> Visita <a href=\\\"https://github.com/azlux/botamusique\\\">la nostra repository GitHub</a> per ulteriori dettagli!\",\n        \"url\": \"URL\",\n        \"url_ban\": \"URL {url} è vietato!\",\n        \"url_ban_list\": \"\",\n        \"url_ban_success\": \"\",\n        \"url_from_playlist\": \"URL\",\n        \"url_from_playlist_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a> <i>dalla playlist</i> <a href=\\\"{playlist_url}\\\">{playlist}</a> <i>aggiunto da</i> {user}\",\n        \"url_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a> <i>aggiunto da</i> {user}\",\n        \"url_unban_success\": \"\",\n        \"url_unwhitelist_success\": \"\",\n        \"url_whitelist_list\": \"\",\n        \"url_whitelist_success\": \"\",\n        \"user_ban\": \"Sei bannato, non ti è permesso farlo!\",\n        \"user_ban_list\": \"\",\n        \"user_ban_success\": \"\",\n        \"user_password_set\": \"La tua password è stata aggiornata.\",\n        \"user_unban_success\": \"\",\n        \"web_user_list\": \"I seguenti utenti hanno il privilegio di accedere all'interfaccia web: <br /> {users}\",\n        \"webpage_address\": \"Il tuo indirizzo per accedere all'interfaccia web è <a href=\\\"{address}\\\">{address}</a>\",\n        \"which_command\": \"Intendi <br /> {commands}\",\n        \"wrong_pattern\": \"Espressione regolare non valida: {error}.\",\n        \"yt_no_more\": \"Nessun altro risultato!\",\n        \"yt_query_error\": \"Impossibile consultare YouTube!\",\n        \"yt_result\": \"Risultato ricerca YouTube: {result_table} Usa <i>!sl {{indexes}}</i> per riprodurre l'elemento desiderato. <br />\\\\n<i>!ytquery -n</i> per la pagina successiva.\"\n    },\n    \"web\": {\n        \"action\": \"Azione\",\n        \"add\": \"Aggiungi\",\n        \"add_all\": \"Aggiungi tutto\",\n        \"add_radio\": \"Aggiungi Radio\",\n        \"add_radio_url\": \"Aggiungi URL Radio\",\n        \"add_to_bottom\": \"Aggiungi in fondo\",\n        \"add_to_bottom_of_current_playlist\": \"Aggiungi in fondo alla playlist corrente\",\n        \"add_to_playlist_next\": \"Aggiungi alla playlist subito dopo il brano corrente\",\n        \"add_url\": \"Aggiungi URL\",\n        \"add_youtube_or_soundcloud_url\": \"Aggiungi URL di YouTube o SoundCloud\",\n        \"are_you_really_sure\": \"Sei davvero sicuro?\",\n        \"aria_botamusique_logo\": \"Botamusique Logo: una volpe con due cuffie, che si gode la musica\",\n        \"aria_default_cover\": \"Un quadrato nero con due ottave unite insieme.\",\n        \"aria_empty_box\": \"Il disegno di una scatola vuota.\",\n        \"aria_remove_this_song\": \"Rimuovi questo brano dalla playlist corrente\",\n        \"aria_skip_current_song\": \"Salta il brano corrente e riproduci ora questo brano\",\n        \"aria_skip_to_next_track\": \"Passa alla traccia successiva\",\n        \"aria_spinner\": \"Una ruota di caricamento\",\n        \"aria_warning_of_deletion\": \"Avviso sulla cancellazione dei file.\",\n        \"autoplay\": \"Riproduzione automatica\",\n        \"browse_music_file\": \"Sfoglia file musicali\",\n        \"cancel\": \"Annulla\",\n        \"cancel_upload_warning\": \"<strong>Sei davvero sicuro?</strong> <br /> Fare di nuovo clic per interrompere il caricamento.\",\n        \"change_playback_mode\": \"Cambia modalità di riproduzione\",\n        \"choose_file\": \"Scegli il file\",\n        \"clear_playlist\": \"Cancella playlist\",\n        \"close\": \"Chiudi\",\n        \"delete_all\": \"Cancella tutto\",\n        \"delete_all_files\": \"Elimina tutti i file elencati\",\n        \"delete_file_warning\": \"Tutti i file elencati qui, inclusi i file in altre pagine, verranno eliminati dal disco rigido.\\n                                È questo che vuoi?\",\n        \"directory\": \"Directory\",\n        \"download_all\": \"Scarica tutto\",\n        \"download_song_from_library\": \"Scarica il brano dalla libreria\",\n        \"edit_submit\": \"Modifica!\",\n        \"edit_tags_for\": \"Modifica tag per\",\n        \"expand_playlist\": \"Vedi elemento <span\\n                                    class=\\\\\\\"playlist-expand-item-range\\\\\\\"></span> nella playlist.\",\n        \"file\": \"File\",\n        \"filters\": \"Filtri\",\n        \"index\": \"#\",\n        \"keywords\": \"Parole chiave\",\n        \"keywords_placeholder\": \"Parole chiave...\",\n        \"mini_player_title\": \"In riproduzione...\",\n        \"music_library\": \"Libreria musicale\",\n        \"next_to_play\": \"Brano seguente\",\n        \"no_tag\": \"Nessun tag\",\n        \"oneshot\": \"One-shot\",\n        \"open_volume_controls\": \"Apri i controlli del volume\",\n        \"page_title\": \"Interfaccia Web di botamusique\",\n        \"pause\": \"Pausa\",\n        \"play\": \"Play\",\n        \"playlist_controls\": \"Controlli playlist\",\n        \"radio\": \"Radio\",\n        \"radio_url_placeholder\": \"URL Radio...\",\n        \"random\": \"Casuale\",\n        \"remove_song_from_library\": \"Rimuovi brano dalla libreria\",\n        \"repeat\": \"Ripeti\",\n        \"rescan_files\": \"Riesegui la scansione dei file\",\n        \"skip_track\": \"Salta traccia\",\n        \"submit\": \"Invia\",\n        \"tags\": \"Tag\",\n        \"tags_to_add\": \"Tag da aggiungere\",\n        \"title\": \"Titolo\",\n        \"token\": \"Token\",\n        \"token_required\": \"Token richiesto\",\n        \"token_required_message\": \"Stai accedendo all'interfaccia web di {{ name }}.\\nÈ necessario un token per concederti l'accesso.<br />\\nPer favore invia \\\\\\\"{{ command }}\\\\\\\" al bot in mumble per acquisirne uno.\",\n        \"type\": \"Genere\",\n        \"upload_file\": \"Carica file\",\n        \"upload_submit\": \"Carica!\",\n        \"upload_to\": \"Carica in\",\n        \"uploaded_finished\": \"Caricamento terminato!\",\n        \"uploading_files\": \"Caricamento file...\",\n        \"url\": \"URL\",\n        \"url_path\": \"Url/Percorso\",\n        \"url_placeholder\": \"URL...\",\n        \"volume_slider\": \"Cursore del volume\"\n    }\n}"
  },
  {
    "path": "lang/ja_JP.json",
    "content": "{\n    \"cli\": {\n        \"added_tags\": \"<b>{song}</b>に<i>{tags}</i>というタグを追加しました。\",\n        \"added_tags_to_all\": \"再生リストの曲に<i>{tags}</i>というタグを追加しました。\",\n        \"admin_help\": \"<h3>管理者コマンド</h3>\\n<b>Bot</b>\\n<ul>\\n<li><b>!<u>k</u>ill </b> - botを終了する。</li>\\n<li><b>!update </b> - 自動更新する。</li>\\n<li><b>!userban </b> {user}  - このユーザーを禁止する。</li>\\n<li><b>!userunban </b> {user}  - このユーザーの禁止を解除する。</li>\\n<li><b>!urlbanlist </b>  - 禁止さらたユーザーリスト</li>\\n<li><b>!urlban </b> [{url}]  - {url} （デフォルトは今の曲のURL）を禁止する。ライブラリに削除する。</li>\\n<li><b>!urlunban </b> {url}  - このURLの禁止を解除する。</li>\\n<li><b>!rescan </b> {url}  - 本機の音楽フォルダをスキャン直す。</li>\\n<li><b>!dropdatabase</b> - 全部設定とライブラリを消去する。</li>\\n</ul>\\n<b>Web Interface</b>\\n<ul>\\n<li><b>!<u>webuserlist</u></b> - list all users that have the permission of accessing the web interface, if auth mode is 'password'.</li>\\n<li><b>!<u>webuseradd</u> {nick name}</b> - grant the user with {nick name} the access to the web interface, if auth mode is 'password'.</li>\\n<li><b>!<u>webuserdel</u> {nick name}</b> - revoke the access to the web interface of {nick name}, if auth mode is 'password'.</li>\\n</ul>\",\n        \"auto_paused\": \"音楽を再開するには、<i>!play</i> を送信してください。\",\n        \"bad_command\": \"<i>{command}</i>: コマンドが見つかりません。\",\n        \"bad_parameter\": \"<i>{command}</i>: パラメータが不正です。\",\n        \"bad_url\": \"URLが不正です。\",\n        \"cache_refreshed\": \"キャッシュが更新されました。\",\n        \"change_ducking_volume\": \"{user}は「ダッキング」が触発する時の音量を{volume}に設定しました。\",\n        \"change_max_volume\": \"\",\n        \"change_mode\": \"{user}がプレイモードを<i>{mode}</i>に設定しました。\",\n        \"change_volume\": \"{user}が音量を{volume}に設定しました。\",\n        \"cleared\": \"再生リストがクリアされました。\",\n        \"cleared_tags\": \"<b>{song}</b>のタグが全部クリアされました。\",\n        \"cleared_tags_from_all\": \"再生リスト内の全ての曲のタグがクリアされました。\",\n        \"command_disabled\": \"{command}: この命令は利用できません。\",\n        \"current_ducking_volume\": \"「ダッキング」が触発する時の音量：{volume}。\",\n        \"current_max_volume\": \"\",\n        \"current_mode\": \"現在のプレイモードは<i>{mode}</i>です。\",\n        \"current_volume\": \"現在の音量は{volume}です。\",\n        \"database_dropped\": \"データベースがクリアされました。\",\n        \"download_in_progress\": \"今は<b>{item}</b>をダウンロード中…\",\n        \"error_executing_command\": \"{command}: コマンドが失敗しまいました，エラーは {error}。\",\n        \"file\": \"ファイル\",\n        \"file_added\": \"新しい曲が追加しました：{item}。\",\n        \"file_deleted\": \"{item}がライブラリから削除されました。\",\n        \"file_item\": \"<b>{artist} - {title}</b>，<i>{user}</i>によって追加しました。\",\n        \"file_missed\": \"'{file}' が見つかりません！プレイリストから削除します。\",\n        \"help\": \"<h3>コマンドの使い方</h3> <br>\\n\\n<b>botを操縦する</b>\\n\\n<ul>\\n<li> <b>!<u>w</u>eb</b> - ウェブインターフェースのアドレスを取得する。 </li>\\n<li> <b>!play </b> （= <b>!p</b>） [{num}] [{start_from}] - 再生を再開する・第{num}番目を再生する。 </li>\\n<li> <b>!<u>pa</u>use </b> - 一時停止。 </li>\\n<li> <b>!<u>st</u>op </b> - 再生停止。 </li>\\n<li> <b>!<u>sk</u>ip </b> - 次の曲にスキップする。 </li>\\n<li> <b>!<u>la</u>st </b> - 最後の曲にスキップする。 </li>\\n<li> <b>!<u>v</u>olume </b> {volume} - 音量を取得・設定する（0〜100）。 </li>\\n<li> <b>!<u>m</u>ode </b> [{mode}] - 再生モードを設定する。 {mode} は<i>one-shot</i> 、 <i>repeat</i>、 <i>random</i>、 \\n<i>autoplay</i> 四つ中の一つです。</li>\\n<li> <b>!duck </b> on/off - 「ダッキング」を起動する（人が喋る時自動的に音量を下げる）。 </li>\\n<li> <b>!duckv </b> {volume} - 「ダッキング」の音量を取得・設定する（0〜100）。 </li>\\n<li> <b>!<u>duckt</u>hres </b> - 「ダッキング」を触発ために必要なオーディオ信号の閾値を設定する（デフォルトは3000）。 </li>\\n<li> <b>!<u>o</u>ust </b> - 再生を停止する、そして最初のチャネルに戻る。 </li>\\n</ul> <br>\\n\\n<b>再生リスト</b> <br>\\n\\n<ul>\\n<li> <b>!<u>n</u>ow </b> (＝ <b>!np</b>) - 今放送中の曲のインフォを取得する。 </li>\\n<li> <b>!<u>q</u>ueue </b> - 再生リストを表示する。 </li>\\n<li> <b>!<u>t</u>ag </b> {tags} - ライブラリの中にタグ「{tags}」がある曲を再生リストに追加する。 </li>\\n<li> <b>!file </b>(＝ <b>!f</b>) {path/folder/keyword} - 本機にある音楽フェイル・フォルダを追加する。 </li>\\n<li> <b>!<u>filem</u>atch </b>(or <b>!fm</b>) {pattern} - ファイルパスが正規表現パターン「{pattern}」にマッチされる曲を追加する。 </li>\\n<li> <b>!<u>ur</u>l </b> {url} - Youtube/SoundCloudリンクを追加する。 </li>\\n<li> <b>!<u>playl</u>ist </b> {url} [{offset}] - Youtube/SoundCloud再生リストを追加する。 </li>\\n<li> <b>!<u>rad</u>io </b> {url} - アドレス「{url}」のウェブラジオを追加する。 </li>\\n<li> <b>!<u>rbq</u>uery </b> {keyword} - http://www.radio-browser.infoからウェブラジオを検索する。</li>\\n<li> <b>!<u>rbp</u>lay </b> {id} - ID「{id}」のウェブラジオを追加する （例： !rbplay 96746）。 </li>\\n<li> <b>!<u>ys</u>earch </b> {keywords} - Youtubeを検索する。 ペイジをめぐるため　<i>!ysearch -n</i> を使ってください。 </li>\\n<li> <b>!<u>yp</u>lay </b> {keywords} - Youtubeを検索する。第一番目の曲を直接に再生リストに追加する。</li>\\n<li> <b>!<u>sh</u>ortlist </b> (or <b>!sl</b>) {indexes/*} - 候補リストの第{indexes}番目の曲を追加する（もし「*」を使ったら、候補リストにある全ての曲を追加する）。 </li>\\n<li> <b>!rm </b> {num} - 再生リストにある第{num}番目の曲を削除する。 </li>\\n<li> <b>!<u>rep</u>eat </b> [{num}] - 今の曲を{num}回リピートする（デフォルトは一回リピートする）。</li>\\n<li> <b>!<u>ran</u>dom </b> - 再生リストの順序をランダム化にする。</li>\\n</ul> <br>\\n\\n<b>ライブリ</b> <br>\\n\\n<ul>\\n<li> <b>!<u>se</u>arch </b> {keywords} - ライブリの中に「{keywords}」が出る曲を検索する。</li>\\n<li> <b>!<u>li</u>stfile </b> [{pattern}] - ファイルパスが正規表現パターン「{pattern}」にマッチされる曲を表示する。  </li>\\n<li> <b>!<u>addt</u>ag </b> [{index}] {tags} - タグ「{tags}」を第{index}番目の曲に追加する（もし{index}が提供されなかったら、今の曲に追加する）。複数のタグが「,」で区切る。 </li>\\n<li> <b>!<u>addt</u>ag </b> * {tags} - タグ「{tags}」を再生リストにある全部曲に追加する。 </li>\\n<li> <b>!<u>un</u>tag </b> [{index/*}] {tags}/* - 第{index}番目の曲（全ての曲、もし「*」を使ったら）からタグ「{tags}」を削除する（全部のタグ、もし「*」を使ったら）。 </li>\\n<li> <b>!<u>fin</u>dtagged </b> (or <b>!ft</b>) {tags} - ライブリに{tags}が含む曲を検索する。 </li>\\n<li> <b>!<u>del</u>ete </b> {index} - ライブリ（ハードドライブ）に候補リストの第{index}番目曲を削除する。 </li>\\n</ul> <br>\\n\\n<b>他のコマンド</b> <br>\\n\\n<ul>\\n<li> <b>!<u>j</u>oinme [{token}] </b> - あなたがいるチャネルに入る。</li>\\n<li> <b>!<u>password</u> {password} </b> - あなたのウェブインタフェーイスのパスワードを変更する。</li>\\n</ul>\",\n        \"invalid_index\": \"インデックス<i>{index}</i>が不正です。再生リストを見るために、<i>!queue</i>を送信してください。\",\n        \"last_song_on_the_queue\": \"最後の曲。\",\n        \"max_volume\": \"\",\n        \"multiple_file_added\": \"以下の曲が追加しました：\",\n        \"multiple_file_deleted\": \"以下の曲がライブラリから削除されました：\",\n        \"multiple_file_found\": \"以下の曲が見つかりました：\",\n        \"multiple_matches\": \"ファイルが見つかりませんでした。もしかして：\",\n        \"new_version_found\": \"<h2>新バージョン発見！</h2> botamusique  {new_version} 可用！ <hr />\\n<h3>更新履歴</h3> {changelog} <hr /> <i>!update</i>を送信してこのバージョンにアップデートします。\",\n        \"next_to_play\": \"次の曲。\",\n        \"no_file\": \"ファイルが見つかりません。\",\n        \"not_admin\": \"あなたは管理員ではありません。\",\n        \"not_in_my_channel\": \"あなたは私のチャネルにいません。\",\n        \"not_playing\": \"何も再生していません。\",\n        \"now_playing\": \"再生中：{item}\",\n        \"page_instruction\": \"第{current}/{total}頁。 <i>!{command} {{page}}</i>を送信してページをめぐります。\",\n        \"paused\": \"音楽は一時停止しました。\",\n        \"playlist_fetching_failed\": \"再生リストを取得できません。\",\n        \"pm_not_allowed\": \"プライベートメッセージが受け取りません。\",\n        \"position_in_the_queue\": \"位置：\",\n        \"preconfigurated_radio\": \"デフォルトのウェブラジオは：\",\n        \"queue_contents\": \"再生リストにある曲は：\",\n        \"queue_empty\": \"再生リストは空です。\",\n        \"radio\": \"ラジオ\",\n        \"radio_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a>（ <i>{name}</i>から）。<i>{user}</i>に追加されました。\",\n        \"rb_play_empty\": \"ラジオIDを提供してください。\",\n        \"rb_query_result\": \"検索の結果（<i> !rbplay {ID} </i>を送信して再生する）\",\n        \"records_omitted\": \"…\",\n        \"removed_tags\": \"<b>{song}</b>からタグ「 <i>{tags}</i>」を削除しました。\",\n        \"removed_tags_from_all\": \"再生リストの全ての曲にタグ「<i>{tags}</i> 」を削除しました。\",\n        \"removing_item\": \"再生リストに「{item}」を削除しました。\",\n        \"repeat\": \"「{song}」を{n}回リピートするになります。\",\n        \"report_version\": \"現在のbotamusiqueバージョンは<b>{version}</b>です。\",\n        \"shortlist_instruction\": \"<i>!sl {indexes}</i>を使ってこのリストの曲を再生する。\",\n        \"start_updating\": \"更新しています…\",\n        \"stopped\": \"再生停止。\",\n        \"too_long\": \"「{song}」が長さ制限を超えました（{duration} > {max_duration}）。削除されました。\",\n        \"unable_download\": \"「{item}」がダウンロードできません。削除されました。\",\n        \"unable_play\": \"「{item}」が再生できません。削除されました。\",\n        \"unknown_mode\": \"不正な再生モード「{mode}」。 <i>one-shot</i>, <i>repeat</i>, <i>random</i>, <i>autoplay</i>の中の一つを使ってください。\",\n        \"update_successful\": \"<h2>botamusique v{version} インストール完成！</h2><hr />\\n<h3>更新履歴</h3> {changelog} <hr /> このプロジェクトの <a href=\\\"https://github.com/azlux/botamusique\\\">githubページ</a> をご覧ください！\",\n        \"url\": \"URL\",\n        \"url_ban\": \"URL {url} が禁止されています。\",\n        \"url_ban_list\": \"\",\n        \"url_ban_success\": \"\",\n        \"url_from_playlist\": \"URL\",\n        \"url_from_playlist_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a>、（<a href=\\\"{playlist_url}\\\">{playlist}</a>から）、 <i>{user} </i>に追加されました。\",\n        \"url_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a>，<i> {user} </i>に追加されました。\",\n        \"url_unban_success\": \"\",\n        \"url_unwhitelist_success\": \"\",\n        \"url_whitelist_list\": \"\",\n        \"url_whitelist_success\": \"\",\n        \"user_ban\": \"あなたはブラックリストに載っています。命令が拒否されました。\",\n        \"user_ban_list\": \"\",\n        \"user_ban_success\": \"\",\n        \"user_password_set\": \"パスワードが更新されました。\",\n        \"user_unban_success\": \"\",\n        \"web_user_list\": \"以下のユーザーはウェブインターフェースを訪問する権利を持っています：<br> {users}\",\n        \"webpage_address\": \"ウェブインターフェースのアドレスは<a href=\\\"{address}\\\">{address}</a>。\",\n        \"which_command\": \"もしかして　<br /> {commands}\",\n        \"wrong_pattern\": \"不正な正規表現パターン：{error}。\",\n        \"yt_no_more\": \"これ以上のエントリがありません。\",\n        \"yt_query_error\": \"Youtubeを訪問できません！\",\n        \"yt_result\": \"Youtube検索結果： {result_table}  <i>!sl {{indexes}}</i>を使って再生します。 <br />\\n<i>!ytquery -n</i>を使ってページをめぐります。\"\n    },\n    \"web\": {\n        \"action\": \"動作\",\n        \"add\": \"追加する\",\n        \"add_all\": \"全部追加\",\n        \"add_radio\": \"ラジオを追加する\",\n        \"add_radio_url\": \"ラジオURL\",\n        \"add_to_bottom\": \"最後尾に追加する。\",\n        \"add_to_bottom_of_current_playlist\": \"再施リストの最後尾に追加する。\",\n        \"add_to_playlist_next\": \"次の曲に追加する。\",\n        \"add_url\": \"URLを追加する\",\n        \"add_youtube_or_soundcloud_url\": \"Youtube・Soundcloud URLを追加する\",\n        \"are_you_really_sure\": \"本当ですが？\",\n        \"aria_botamusique_logo\": \"BotamusiqueのLogo\",\n        \"aria_default_cover\": \"デフォルトアルバムカバー。\",\n        \"aria_empty_box\": \"空。\",\n        \"aria_remove_this_song\": \"再生リストからこの曲を削除する。\",\n        \"aria_skip_current_song\": \"いますぐこの曲を再生する。\",\n        \"aria_skip_to_next_track\": \"次の曲を再生する。\",\n        \"aria_spinner\": \"ローディング中\",\n        \"aria_warning_of_deletion\": \"ファイル削除警告\",\n        \"autoplay\": \"自動再生\",\n        \"browse_music_file\": \"音楽ファイルを閲覧する\",\n        \"cancel\": \"キャンセル\",\n        \"cancel_upload_warning\": \"<strong>本当ですが？</strong> <br /> もしアップ本当にロードをキャンセルすることに決まったら、もう一度このバトンを押してください。\",\n        \"change_playback_mode\": \"再生モードを変更する\",\n        \"choose_file\": \"ファイルを選ぶ\",\n        \"clear_playlist\": \"クリアする\",\n        \"close\": \"閉める\",\n        \"delete_all\": \"全部削除\",\n        \"delete_all_files\": \"以上のファイルを全て削除する\",\n        \"delete_file_warning\": \"続行すると、以上表示されたファイル（他のページにあるファイルも含む）をハードドライブから消去されます。本当にそうしますか？\",\n        \"directory\": \"フォルダ\",\n        \"download_all\": \"全部ダウンロード\",\n        \"download_song_from_library\": \"ライブラリから音楽ファイルをダウンロードする\",\n        \"edit_submit\": \"変更\",\n        \"edit_tags_for\": \"タグを編集する\",\n        \"expand_playlist\": \"第 <span class=\\\"playlist-expand-item-range\\\"></span> 番目の曲を表示する。\",\n        \"file\": \"ファイル\",\n        \"filters\": \"検索\",\n        \"index\": \"＃\",\n        \"keywords\": \"キーワード\",\n        \"keywords_placeholder\": \"キーワード…\",\n        \"mini_player_title\": \"放送中…\",\n        \"music_library\": \"ライブラリ\",\n        \"next_to_play\": \"次の曲に追加する\",\n        \"no_tag\": \"空\",\n        \"oneshot\": \"順番に再生\",\n        \"open_volume_controls\": \"音量スライダーを表示する\",\n        \"page_title\": \"botamusiqueウェブインタフェイス\",\n        \"pause\": \"一時停止\",\n        \"play\": \"再生する\",\n        \"playlist_controls\": \"再生管理\",\n        \"radio\": \"ラジオ\",\n        \"radio_url_placeholder\": \"ラジオURL…\",\n        \"random\": \"シャッフル再生\",\n        \"remove_song_from_library\": \"ライブラリから削除する\",\n        \"repeat\": \"全曲リピート\",\n        \"rescan_files\": \"フォルダをスキャン直す\",\n        \"skip_track\": \"今の曲をスッキプする\",\n        \"submit\": \"送信\",\n        \"tags\": \"タグ\",\n        \"tags_to_add\": \"追加するタグ\",\n        \"title\": \"タイトル\",\n        \"token\": \"トークン\",\n        \"token_required\": \"トークンが必要です\",\n        \"token_required_message\": \"このページは{{ name }}のウェブインタフェイスです。\\n設定によって、ログオンするにはトークンが必要になります。<br />\\n \\\"{{ command }}\\\" を送信してトークンを取得してください。\",\n        \"type\": \"種類\",\n        \"upload_file\": \"アップロード\",\n        \"upload_submit\": \"アップロード\",\n        \"upload_to\": \"フォルダ\",\n        \"uploaded_finished\": \"アップロード完了\",\n        \"uploading_files\": \"アップロード中…\",\n        \"url\": \"URL\",\n        \"url_path\": \"URL・パス\",\n        \"url_placeholder\": \"URL…\",\n        \"volume_slider\": \"音量スライダー\"\n    }\n}"
  },
  {
    "path": "lang/nl_NL.json",
    "content": "{\n    \"cli\": {\n        \"added_tags\": \"\",\n        \"added_tags_to_all\": \"\",\n        \"admin_help\": \"\",\n        \"auto_paused\": \"\",\n        \"bad_command\": \"\",\n        \"bad_parameter\": \"\",\n        \"bad_url\": \"\",\n        \"cache_refreshed\": \"\",\n        \"change_ducking_volume\": \"\",\n        \"change_max_volume\": \"\",\n        \"change_mode\": \"\",\n        \"change_volume\": \"\",\n        \"cleared\": \"\",\n        \"cleared_tags\": \"\",\n        \"cleared_tags_from_all\": \"\",\n        \"command_disabled\": \"\",\n        \"current_ducking_volume\": \"\",\n        \"current_max_volume\": \"\",\n        \"current_mode\": \"\",\n        \"current_volume\": \"Huidig volume: {volume}.\",\n        \"database_dropped\": \"\",\n        \"download_in_progress\": \"\",\n        \"error_executing_command\": \"\",\n        \"file\": \"Bestand\",\n        \"file_added\": \"Toegevoegd {item}.\",\n        \"file_deleted\": \"\",\n        \"file_item\": \"\",\n        \"file_missed\": \"\",\n        \"help\": \"\",\n        \"invalid_index\": \"\",\n        \"last_song_on_the_queue\": \"\",\n        \"max_volume\": \"\",\n        \"multiple_file_added\": \"\",\n        \"multiple_file_deleted\": \"\",\n        \"multiple_file_found\": \"\",\n        \"multiple_matches\": \"\",\n        \"new_version_found\": \"\",\n        \"next_to_play\": \"\",\n        \"no_file\": \"\",\n        \"not_admin\": \"\",\n        \"not_in_my_channel\": \"\",\n        \"not_playing\": \"\",\n        \"now_playing\": \"\",\n        \"page_instruction\": \"\",\n        \"paused\": \"\",\n        \"playlist_fetching_failed\": \"\",\n        \"pm_not_allowed\": \"\",\n        \"position_in_the_queue\": \"\",\n        \"preconfigurated_radio\": \"\",\n        \"queue_contents\": \"\",\n        \"queue_empty\": \"\",\n        \"radio\": \"\",\n        \"radio_item\": \"\",\n        \"rb_play_empty\": \"\",\n        \"rb_query_result\": \"\",\n        \"records_omitted\": \"\",\n        \"removed_tags\": \"\",\n        \"removed_tags_from_all\": \"\",\n        \"removing_item\": \"\",\n        \"repeat\": \"\",\n        \"report_version\": \"\",\n        \"shortlist_instruction\": \"\",\n        \"start_updating\": \"\",\n        \"stopped\": \"\",\n        \"too_long\": \"\",\n        \"unable_download\": \"\",\n        \"unable_play\": \"\",\n        \"unknown_mode\": \"\",\n        \"update_successful\": \"\",\n        \"url\": \"\",\n        \"url_ban\": \"\",\n        \"url_ban_list\": \"\",\n        \"url_ban_success\": \"\",\n        \"url_from_playlist\": \"\",\n        \"url_from_playlist_item\": \"\",\n        \"url_item\": \"\",\n        \"url_unban_success\": \"\",\n        \"url_unwhitelist_success\": \"\",\n        \"url_whitelist_list\": \"\",\n        \"url_whitelist_success\": \"\",\n        \"user_ban\": \"\",\n        \"user_ban_list\": \"\",\n        \"user_ban_success\": \"\",\n        \"user_password_set\": \"\",\n        \"user_unban_success\": \"\",\n        \"web_user_list\": \"\",\n        \"webpage_address\": \"\",\n        \"which_command\": \"\",\n        \"wrong_pattern\": \"\",\n        \"yt_no_more\": \"\",\n        \"yt_query_error\": \"\",\n        \"yt_result\": \"\"\n    },\n    \"web\": {\n        \"action\": \"\",\n        \"add\": \"\",\n        \"add_all\": \"\",\n        \"add_radio\": \"\",\n        \"add_radio_url\": \"\",\n        \"add_to_bottom\": \"\",\n        \"add_to_bottom_of_current_playlist\": \"\",\n        \"add_to_playlist_next\": \"\",\n        \"add_url\": \"\",\n        \"add_youtube_or_soundcloud_url\": \"\",\n        \"are_you_really_sure\": \"\",\n        \"aria_botamusique_logo\": \"\",\n        \"aria_default_cover\": \"\",\n        \"aria_empty_box\": \"\",\n        \"aria_remove_this_song\": \"\",\n        \"aria_skip_current_song\": \"\",\n        \"aria_skip_to_next_track\": \"\",\n        \"aria_spinner\": \"\",\n        \"aria_warning_of_deletion\": \"\",\n        \"autoplay\": \"\",\n        \"browse_music_file\": \"\",\n        \"cancel\": \"\",\n        \"cancel_upload_warning\": \"\",\n        \"change_playback_mode\": \"\",\n        \"choose_file\": \"\",\n        \"clear_playlist\": \"\",\n        \"close\": \"\",\n        \"delete_all\": \"\",\n        \"delete_all_files\": \"\",\n        \"delete_file_warning\": \"\",\n        \"directory\": \"\",\n        \"download_all\": \"\",\n        \"download_song_from_library\": \"\",\n        \"edit_submit\": \"\",\n        \"edit_tags_for\": \"\",\n        \"expand_playlist\": \"\",\n        \"file\": \"\",\n        \"filters\": \"\",\n        \"index\": \"\",\n        \"keywords\": \"\",\n        \"keywords_placeholder\": \"\",\n        \"mini_player_title\": \"\",\n        \"music_library\": \"\",\n        \"next_to_play\": \"\",\n        \"no_tag\": \"\",\n        \"oneshot\": \"\",\n        \"open_volume_controls\": \"\",\n        \"page_title\": \"\",\n        \"pause\": \"\",\n        \"play\": \"\",\n        \"playlist_controls\": \"\",\n        \"radio\": \"\",\n        \"radio_url_placeholder\": \"\",\n        \"random\": \"\",\n        \"remove_song_from_library\": \"\",\n        \"repeat\": \"\",\n        \"rescan_files\": \"\",\n        \"skip_track\": \"\",\n        \"submit\": \"\",\n        \"tags\": \"\",\n        \"tags_to_add\": \"\",\n        \"title\": \"\",\n        \"token\": \"\",\n        \"token_required\": \"\",\n        \"token_required_message\": \"\",\n        \"type\": \"\",\n        \"upload_file\": \"\",\n        \"upload_submit\": \"\",\n        \"upload_to\": \"\",\n        \"uploaded_finished\": \"\",\n        \"uploading_files\": \"\",\n        \"url\": \"\",\n        \"url_path\": \"\",\n        \"url_placeholder\": \"\",\n        \"volume_slider\": \"\"\n    }\n}"
  },
  {
    "path": "lang/pt_BR.json",
    "content": "{\n    \"cli\": {\n        \"added_tags\": \"As etiquetas <i>{tags}</i> foram adicionadas em <b>{song}</b>.\",\n        \"added_tags_to_all\": \"As etiquetas <i>{tags}</i> foram adicionadas nas músicas da lista de reprodução.\",\n        \"admin_help\": \"<h3>Comandos de administrador</h3>\\n<b>Robô</b>\\n<ul>\\n<li><b>!<u>k</u>ill </b> - matar o robô</li>\\n<li><b>!update </b> - atualizar o robô</li>\\n<li><b>!userban </b> {usuário}  - banir um usuário</li>\\n<li><b>!userunban </b> {usuário}  - remover usuário da lista de usuários banidos</li>\\n<li><b>!urlbanlist </b>  - exibir lista de endereços banidos</li>\\n<li><b>!urlban </b> [{endereço}]  - banir {endereço} (ou o endereço do item atual, por padrão) e remover este endereço da biblioteca.</li>\\n<li><b>!urlunban </b> {endereço  - remover {endereço} da lista de endereços banidos</li>\\n<li><b>!rescan </b> {endereço}  - reconstruir cache de arquivos de música local</li>\\n<li><b>!dropdatabase</b> - limpar o banco de dados inteiro, você perderá todas as configurações e a biblioteca de música.</li>\\n</ul>\\n<b>Interface web</b>\\n<ul>\\n<li><b>!<u>webuserlist</u></b> - exibir lista de todos os usuários que têm permissão para acessar a interface web, se o modo de autenticação for 'password'.</li>\\n<li><b>!<u>webuseradd</u> {apelido}</b> - dar acesso à interface web para {apelido}, se o modo de autenticação for 'password'.</li>\\n<li><b>!<u>webuserdel</u> {apelido}</b> - revogar o acesso à interface web de {apelido}, caso o modo de autenticação for 'password'.</li>\\n</ul>\",\n        \"auto_paused\": \"Use <i>!play</i> para retomar a reprodução de música!\",\n        \"bad_command\": \"<i>{command}</i>: comando não encontrado.\",\n        \"bad_parameter\": \"<i>{command}</i>: parâmetro inválido.\",\n        \"bad_url\": \"Um endereço malformado foi pedido.\",\n        \"cache_refreshed\": \"Cache atualizado!\",\n        \"change_ducking_volume\": \"O volume de atenuação foi definido para {volume} por {user}.\",\n        \"change_max_volume\": \"O volume máximo foi definido para {max} por {user}.\",\n        \"change_mode\": \"O modo de reprodução foi definido para <i>{mode}</i> por {user}.\",\n        \"change_volume\": \"O volume foi definido para {volume} por {user}.\",\n        \"cleared\": \"A lista de reprodução foi esvaziada.\",\n        \"cleared_tags\": \"Todas as etiquetas foram removidas de <b>{song}</b>.\",\n        \"cleared_tags_from_all\": \"Todas as etiquetas das músicas na lista de reprodução foram removidas.\",\n        \"command_disabled\": \"{command}: comando desabilitado!\",\n        \"current_ducking_volume\": \"Volume de atenuação: {volume}.\",\n        \"current_max_volume\": \"Volume máximo atual: {max}.\",\n        \"current_mode\": \"O modo de reprodução é <i>{mode}</i>.\",\n        \"current_volume\": \"Volume atual: {volume}.\",\n        \"database_dropped\": \"O banco de dados foi esvaziado.\",\n        \"download_in_progress\": \"A descarga de <b>{item}</b> está em progresso...\",\n        \"error_executing_command\": \"{command}: O comando falhou com um erro: {error}.\",\n        \"file\": \"Arquivo\",\n        \"file_added\": \"{item} adicionado.\",\n        \"file_deleted\": \"{item} foi apagado da biblioteca.\",\n        \"file_item\": \"<b>{artist} - {title}</b> <i>adicionado por</i> {user}\",\n        \"file_missed\": \"O arquivo de música '{file}' foi perdido! Este item foi removido da lista de reprodução.\",\n        \"help\": \"<h3>Comandos</h3>\\n<b>Controle</b>\\n<ul>\\n<li> <b>!<u>w</u>eb</b> - exibe o endereço da interface web, caso habilitado. </li>\\n<li> <b>!play </b> (ou <b>!p</b>) [{num}] [{iniciar_de}] - resume/inicia a reprodução (a partir da música na posição {num}, caso especificado) </li>\\n<li> <b>!<u>pa</u>use </b> - pausa </li>\\n<li> <b>!<u>st</u>op </b> - interrompe a reprodução </li>\\n<li> <b>!<u>sk</u>ip </b> - pula para a próxima música </li>\\n<li> <b>!<u>la</u>st </b> - pula para a última música </li>\\n<li> <b>!<u>v</u>olume </b> {volume} - exibe ou altera o volume (de 0 a 100) </li>\\n<li> <b>!<u>m</u>ode </b> [{modo}] - exibe ou define o modo de reprodução, {modo} deve ser um dos seguintes: <i>one-shot</i> (remover o item assim que ele for reproduzido, <i>repeat</i> (repetir a lista de reprodução), ou <i>random</i> (tornar a lista de reprodução em ordem aleatória),\\n<i>autoplay</i> (escolher algo da biblioteca de música aleatoriamente).</li>\\n<li> <b>!duck </b> on/off - habilita ou desabilita a função de atenuação </li>\\n<li> <b>!duckv </b> {volume} - define o volume do robô quando a atenuação está ativada </li>\\n<li> <b>!<u>duckt</u>hres </b> - define o nível de volume que ativa a atenuação (3000 por padrão)</li>\\n<li> <b>!<u>o</u>ust </b> - interrompe a reprodução e vai para o canal padrão </li>\\n</ul>\\n<b>Lista de reprodução</b>\\n<ul>\\n<li> <b>!<u>n</u>ow </b> (ou <b>!np</b>) - exibe a música atual </li>\\n<li> <b>!<u>q</u>ueue </b> - exibe os itens na lista de reprodução </li>\\n<li> <b>!<u>t</u>ag </b> {etiquetas} - adiciona todos os itens com as etiquetas {etiquetas}, etiquetas separadas com \\\",\\\". </li>\\n<li> <b>!file </b>(ou <b>!f</b>) {caminho/pasta/palavra-chave} - adiciona um único arquivo à lista de reprodução pelo seu caminho ou palavra-chave em seu caminho. </li>\\n<li> <b>!<u>filem</u>atch </b>(ou <b>!fm</b>) {padrão} - adiciona todos os arquivos que combinarem com a expressão regular {padrão} </li>\\n<li> <b>!<u>ur</u>l </b> {url} - adicionar música do YouTube ou SoundCloud </li>\\n<li> <b>!<u>playl</u>ist </b> {endereço} [{deslocamento}] - adiciona todos os itens em uma lista de reprodução do YouTube ou SoundCloud, a partir do item na posição {deslocamento} </li>\\n<li> <b>!<u>rad</u>io </b> {endereço} - adiciona a rádio {endereço} no final da lista de reprodução </li>\\n<li> <b>!<u>rbq</u>uery </b> {palavra_chave} - busca por uma estação de rádio em http://www.radio-browser.info </li>\\n<li> <b>!<u>rbp</u>lay </b> {id} - reproduz uma estação de rádio com {id} (por ex.: !rbplay 96746) </li>\\n<li> <b>!<u>ys</u>earch </b> {palavras_chave} - busca no YouTube. Use <i>!ysearch -n</i> para trocar de página. </li>\\n<li> <b>!<u>yp</u>lay </b> {palavras_chave} - adiciona o primeiro resultado da busca de {palavras_chave} na lista de reprodução.</li>\\n<li> <b>!<u>sh</u>ortlist </b> (ou <b>!sl</b>) {índices/*} - adiciona o item na posição {índices} (ou todos caso * seja especificado) na lista curta. </li>\\n<li> <b>!rm </b> {num} - remove a música na posição {num} da lista de reprodução </li>\\n<li> <b>!<u>rep</u>eat </b> [{num}] - repete a música atual {num} (1 por padrão) vezes.</li>\\n<li> <b>!<u>ran</u>dom </b> - torna a lista de reprodução em ordem aleatória.</li>\\n</ul>\\n<b>Biblioteca de música</b>\\n<ul>\\n<li> <b>!<u>se</u>arch </b> {palavras_chave} - busca pelo item com {palavras_chave} na biblioteca de música, palavras-chave separadas por espaço.</li>\\n<li> <b>!<u>li</u>stfile </b> [{padrão}] - exibe a lista de arquivos disponíveis (os quais caminhos combinam com o padrão de expressão regular caso {padrão} seja especificado) </li>\\n<li> <b>!<u>addt</u>ag </b> [{índice}] {etiquetas} - adiciona {etiquetas} para a música da lista de reprodução na posição {índice} (ou a música atual caso {índice} seja omitido), etiquetas separadas por \\\",\\\". </li>\\n<li> <b>!<u>addt</u>ag </b> * {etiquetas} - adiciona {etiquetas} para todos os itens na lista de reprodução. </li>\\n<li> <b>!<u>un</u>tag </b> [{índice/*}] {etiquetas}/* - remove {etiquetas}/todas as etiquetas da música da lista de reprodução na posição {índice} (ou a música atual caso {índice} seja omitido). </li>\\n<li> <b>!<u>fin</u>dtagged </b> (ou <b>!ft</b>) {etiquetas} - busca por um item com {etiquetas} na biblioteca de música. </li>\\n<li> <b>!<u>del</u>ete </b> {índice} - apaga da biblioteca de música o item da lista curta na posição {índice}. </li>\\n</ul>\\n<b>Outro</b>\\n<ul>\\n<li> <b>!<u>j</u>oinme {token} </b> - entra no seu próprio canal com {token}.</li>\\n<li> <b>!<u>password</u> {senha} </b> - altera sua senha, usada para acessar a interface web.</li>\\n</ul>\",\n        \"invalid_index\": \"O índice <i>{index}</i> é inválido. Use <i>!queue</i> para visualizar a lista de reprodução.\",\n        \"last_song_on_the_queue\": \"Último na fila.\",\n        \"max_volume\": \"O volume excede o volume máximo {max}. O volume foi definido para o máximo.\",\n        \"multiple_file_added\": \"Múltiplos itens adicionados:\",\n        \"multiple_file_deleted\": \"Múltiplos itens foram apagados da biblioteca:\",\n        \"multiple_file_found\": \"Encontrado:\",\n        \"multiple_matches\": \"Arquivo não encontrado! Possíveis resultados:\",\n        \"new_version_found\": \"<h2>Atualização disponível!</h2> A versão {new_version} do botamusique está disponível! <hr />\\n<h3>Registro de mudanças</h3> {changelog} <hr /> Envie <i>!update</i> para atualizar!\",\n        \"next_to_play\": \"Próxima música.\",\n        \"no_file\": \"Arquivo não encontrado.\",\n        \"not_admin\": \"Você não é um administrador!\",\n        \"not_in_my_channel\": \"Você não está no meu canal!\",\n        \"not_playing\": \"Nada está sendo reproduzido neste momento.\",\n        \"now_playing\": \"Reproduzindo {item}\",\n        \"page_instruction\": \"Página {current}/{total}. Use <i>!{command} {{page}}</i> para navegar.\",\n        \"paused\": \"Música pausada.\",\n        \"playlist_fetching_failed\": \"Não foi possível receber a lista de reprodução!\",\n        \"pm_not_allowed\": \"Mensagens privadas não são permitidas.\",\n        \"position_in_the_queue\": \"Posição: {position}\",\n        \"preconfigurated_radio\": \"Estações de rádio pré-configuradas disponíveis:\",\n        \"queue_contents\": \"Itens na lista de reprodução:\",\n        \"queue_empty\": \"A lista de reprodução está vazia!\",\n        \"radio\": \"Rádio\",\n        \"radio_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a> <i>de</i> {name} <i>foi adicionado por</i> {user}\",\n        \"rb_play_empty\": \"Por favor especifique a identificação de uma estação de rádio!\",\n        \"rb_query_result\": \"Este é o resultado da sua busca, envie <i>!rbplay {ID}</i> para reproduzir uma estação:\",\n        \"records_omitted\": \"…\",\n        \"removed_tags\": \"As etiquetas <i>{tags}</i> foram removidas de <b>{song}</b>.\",\n        \"removed_tags_from_all\": \"As etiquetas <i>{tags}</i> foram removidas das músicas na lista de reprodução.\",\n        \"removing_item\": \"O item {item} na lista de reprodução foi removido.\",\n        \"repeat\": \"Repetir {song} {n} vezes.\",\n        \"report_version\": \"A versão atual do botamusique é <b>{version}</b>.\",\n        \"shortlist_instruction\": \"Use <i>!sl {índices}</i> para reproduzir o item que você deseja.\",\n        \"start_updating\": \"Iniciando a atualização...\",\n        \"stopped\": \"Música parada.\",\n        \"too_long\": \"<b>{song}</b> é muito longo ({duration} > {max_duration}). Removido da lista de reprodução!\",\n        \"unable_download\": \"Falha ao baixar <b>{item}</b>. Removido da biblioteca.\",\n        \"unable_play\": \"Falha ao reproduzir <b>{item}</b>. Removido da biblioteca.\",\n        \"unknown_mode\": \"O modo de reprodução '{mode}' é desconhecido. Ele deve ser um dos seguintes: <i>one-shot</i>, <i>repeat</i>, <i>random</i>.\",\n        \"update_successful\": \"<h2>botamusique v{version} instalado!</h2><hr />\\n<h3>Registro de mudanças</h3> {changelog} <hr /> Visite <a href=\\\"https://github.com/azlux/botamusique\\\">nosso repositório no GitHub</a> para mais detalhes!\",\n        \"url\": \"Endereço\",\n        \"url_ban\": \"O endereço {url} está banido! Removido da lista de reprodução!\",\n        \"url_ban_list\": \"Lista de endereços banidos: <br>{list}\",\n        \"url_ban_success\": \"O seguinte endereço está banido: {url}.\",\n        \"url_from_playlist\": \"Endereço\",\n        \"url_from_playlist_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a> <i>da lista de reprodução</i> <a href=\\\"{playlist_url}\\\">{playlist}</a> <i>adicionado por</i> {user}\",\n        \"url_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a> <i>adicionado por</i> {user}\",\n        \"url_unban_success\": \"O seguinte endereço foi removido da lista de endereços banidos: {url}.\",\n        \"url_unwhitelist_success\": \"O seguinte endereço foi removido da lista branca: {url}.\",\n        \"url_whitelist_list\": \"Lista de endereços na lista branca: <br>{list}\",\n        \"url_whitelist_success\": \"O seguinte endereço foi adicionado à lista branca: {url}.\",\n        \"user_ban\": \"Você está banido. Você não tem permissão para fazer isto!\",\n        \"user_ban_list\": \"Lista de usuários banidos: <br>{list}\",\n        \"user_ban_success\": \"O usuário {user} foi banido.\",\n        \"user_password_set\": \"A sua senha foi atualizada.\",\n        \"user_unban_success\": \"O usuário {user} foi removido da lista de usuários banidos.\",\n        \"web_user_list\": \"Os seguintes usuários possuem privilégio para acessar a interface web: <br /> {users}\",\n        \"webpage_address\": \"O seu próprio endereço para acessar a interface web é <a href=\\\"{address}\\\">{address}</a>\",\n        \"which_command\": \"Você quis dizer <br /> {commands}\",\n        \"wrong_pattern\": \"Expressão regular inválida: {error}.\",\n        \"yt_no_more\": \"Não há mais resultados!\",\n        \"yt_query_error\": \"Não foi possível buscar no YouTube!\",\n        \"yt_result\": \"Resultado da busca no YouTube: {result_table} Use <i>!sl {{índices}}</i> para reproduzir o item que você deseja. <br />\\n<i>!ytquery -n</i> para exibir a próxima página.\"\n    },\n    \"web\": {\n        \"action\": \"Ação\",\n        \"add\": \"Adicionar\",\n        \"add_all\": \"Adicionar todos\",\n        \"add_radio\": \"Adicionar rádio\",\n        \"add_radio_url\": \"Adicionar endereço de rádio\",\n        \"add_to_bottom\": \"Adicionar no fim\",\n        \"add_to_bottom_of_current_playlist\": \"Adicionar no fim da lista de reprodução atual\",\n        \"add_to_playlist_next\": \"Adicionar para a lista de reprodução após a música atual\",\n        \"add_url\": \"Adicionar lista de reprodução\",\n        \"add_youtube_or_soundcloud_url\": \"Adicionar endereço do YouTube ou SoundCloud\",\n        \"are_you_really_sure\": \"Você realmente tem certeza?\",\n        \"aria_botamusique_logo\": \"Logo do botamusique: uma raposa escutando música com fones de ouvido\",\n        \"aria_default_cover\": \"Um quadrado preto com duas oitavas disparadas juntas\",\n        \"aria_empty_box\": \"Um desenho de uma caixa vazia.\",\n        \"aria_remove_this_song\": \"Remover esta música da lista de reprodução atual\",\n        \"aria_skip_current_song\": \"Pular música atual e reproduzir esta música agora\",\n        \"aria_skip_to_next_track\": \"Pular para a próxima trilha\",\n        \"aria_spinner\": \"Um ícone de carregamento girando\",\n        \"aria_warning_of_deletion\": \"Aviso sobre a remoção de arquivos.\",\n        \"autoplay\": \"Reproduzir automaticamente\",\n        \"browse_music_file\": \"Procurar arquivo de música\",\n        \"cancel\": \"Cancelar\",\n        \"cancel_upload_warning\": \"<strong>Você realmente tem certeza?</strong><br />Clique novamente para abortar o envio.\",\n        \"change_playback_mode\": \"Alterar modo de reprodução\",\n        \"choose_file\": \"Escolher arquivo\",\n        \"clear_playlist\": \"Limpar lista de reprodução\",\n        \"close\": \"Fechar\",\n        \"delete_all\": \"Apagar todos\",\n        \"delete_all_files\": \"Apagar todos os arquivos listados\",\n        \"delete_file_warning\": \"Todos os arquivos listados aqui, incluindo os arquivos em outras páginas, serão apagados do seu disco.\\n                                É isso o que você quer?\",\n        \"directory\": \"Diretório\",\n        \"download_all\": \"Baixar todos\",\n        \"download_song_from_library\": \"Baixar música da biblioteca\",\n        \"edit_submit\": \"Editar!\",\n        \"edit_tags_for\": \"Editar etiquetas de\",\n        \"expand_playlist\": \"Ver o item <span\\n                                    class=\\\"playlist-expand-item-range\\\"></span> na lista de reprodução.\",\n        \"file\": \"Arquivo\",\n        \"filters\": \"Filtros\",\n        \"index\": \"Nº\",\n        \"keywords\": \"Palavras-chave\",\n        \"keywords_placeholder\": \"Palavras-chave...\",\n        \"mini_player_title\": \"Reproduzindo...\",\n        \"music_library\": \"Biblioteca de música\",\n        \"next_to_play\": \"Próximo a reproduzir\",\n        \"no_tag\": \"Nenhuma etiqueta\",\n        \"oneshot\": \"Reprodução única\",\n        \"open_volume_controls\": \"Abrir controles de volume\",\n        \"page_title\": \"Interface web botamusique\",\n        \"pause\": \"Pausar\",\n        \"play\": \"Reproduzir\",\n        \"playlist_controls\": \"Controles de lista de reprodução\",\n        \"radio\": \"Rádio\",\n        \"radio_url_placeholder\": \"Endereço de rádio...\",\n        \"random\": \"Aleatório\",\n        \"remove_song_from_library\": \"Remover música da biblioteca\",\n        \"repeat\": \"Repetir\",\n        \"rescan_files\": \"Escanear arquivos novamente\",\n        \"skip_track\": \"Pular trilha\",\n        \"submit\": \"Enviar\",\n        \"tags\": \"Etiquetas\",\n        \"tags_to_add\": \"Etiquetas para adicionar\",\n        \"title\": \"Título\",\n        \"token\": \"Token\",\n        \"token_required\": \"Token necessário\",\n        \"token_required_message\": \"Você está acessando a interface web de {{ name }}.\\nUm token é necessário para autorizar o seu acesso.<br />\\nPor favor, envie \\\"{{ command }}\\\" para o robô no Mumble para recebê-lo.\",\n        \"type\": \"Tipo\",\n        \"upload_file\": \"Enviar arquivo\",\n        \"upload_submit\": \"Enviar!\",\n        \"upload_to\": \"Enviar para\",\n        \"uploaded_finished\": \"Envio concluído!\",\n        \"uploading_files\": \"Enviando arquivos...\",\n        \"url\": \"Endereço\",\n        \"url_path\": \"Endereço/Caminho\",\n        \"url_placeholder\": \"Endereço...\",\n        \"volume_slider\": \"Controle de volume\"\n    }\n}"
  },
  {
    "path": "lang/zh_CN.json",
    "content": "{\n    \"cli\": {\n        \"added_tags\": \"已将标签 <i>{tags}</i> 添加到 <b>{song}</b>。\",\n        \"added_tags_to_all\": \"已将标签 <i>{tags}</i> 添加到播放列表的所有曲目中。\",\n        \"admin_help\": \"<h3>管理员命令</h3>\\n<b>机器人管理</b>\\n<ul>\\n<li><b>!<u>k</u>ill </b> - 退出。</li>\\n<li><b>!update </b> - 自动更新至新版本。</li>\\n<li><b>!userban </b> {user}  - 封禁用户。</li>\\n<li><b>!userunban </b> {user}  - 解除封禁。</li>\\n<li><b>!urlbanlist </b>  - 列出全部封禁的用户。</li>\\n<li><b>!urlban </b> [{url}]  - 封禁链接 {url} （若未指定，则默认为当前播放曲目的URL） 并将它从数据库中移除。</li>\\n<li><b>!urlunban </b> {url}  - 解除封禁链接 {url}。</li>\\n<li><b>!rescan </b> {url}  - 更新本地音乐库。</li>\\n<li><b>!dropdatabase</b> - 清除数据库（包括设置和音乐库）。本操作不可逆，请务必事先考虑清楚。</li>\\n</ul>\\n<b>网络控制界面</b>\\n<ul>\\n<li><b>!<u>webuserlist</u></b> - （若当前认证模式为 'password'）列出所有具有网络控制界面访问权限的用户。</li>\\n<li><b>!<u>webuseradd</u> {name}</b> - （若当前认证模式为 'password'）授权名为 {name} 的用户访问网络控制界面。</li>\\n<li><b>!<u>webuserdel</u> {name}</b> - （若当前认证模式为 'password'）撤销名为 {name} 的用户的访问权限。</li>\\n</ul>\",\n        \"auto_paused\": \"已暂停。若要继续播放，请发送 <i>!play</i> ！\",\n        \"bad_command\": \"{{command}}: 未知命令。请发送<i>!help</i>获取命令列表。\",\n        \"bad_parameter\": \"{command}: 无效参数！\",\n        \"bad_url\": \"URL地址无效！\",\n        \"cache_refreshed\": \"缓存已刷新。\",\n        \"change_ducking_volume\": \"{user}将“闪避”时的音量设置为 {volume}。\",\n        \"change_max_volume\": \"\",\n        \"change_mode\": \"{user}将播放列表模式被设置为<i>{mode}</i> 。\",\n        \"change_volume\": \"{user}将音量设置为{volume}。\",\n        \"cleared\": \"播放列表已清空。\",\n        \"cleared_tags\": \"已移除<b>{song}</b>上的所有标签。\",\n        \"cleared_tags_from_all\": \"已移除播放列表内所有曲目的标签。\",\n        \"command_disabled\": \"{command}: 该命令不可用！\",\n        \"current_ducking_volume\": \"“闪避”时的音量为：{volume}。\",\n        \"current_max_volume\": \"\",\n        \"current_mode\": \"当前的播放模式为<i>{mode}</i>。\",\n        \"current_volume\": \"当前音量为{volume}。\",\n        \"database_dropped\": \"数据库已经清空。\",\n        \"download_in_progress\": \"正在下载<b>{item}</b>……\",\n        \"error_executing_command\": \"{command}: 命令失败，错误为 {error}。\",\n        \"file\": \"文件\",\n        \"file_added\": \"新曲目被添加：{item}。\",\n        \"file_deleted\": \"{item}已从库中移除。\",\n        \"file_item\": \"<b>{artist} - {title}</b>，由<i>{user}</i>添加。\",\n        \"file_missed\": \"文件 '{file}' 丢失！已将其移出播放列表。\",\n        \"help\": \"<h3>命令帮助</h3>\\n\\n<b>播放控制</b>\\n\\n<ul>\\n<li> <b>!<u>w</u>eb</b> - 获取网页控制界面的地址（如果启用了的话）。 </li>\\n<li> <b>!play </b> （或 <b>!p</b>） [{num}] [{start_from}] - 继续播放/开始播放第{num}首曲目。 </li>\\n<li> <b>!<u>pa</u>use </b> - 暂停播放。 </li>\\n<li> <b>!<u>st</u>op </b> - 停止播放。 </li>\\n<li> <b>!<u>sk</u>ip </b> - 跳到下一首曲目。 </li>\\n<li> <b>!<u>la</u>st </b> - 跳到播放列表上的最后一首曲目。 </li>\\n<li> <b>!<u>v</u>olume </b> {volume} - 获取或设置音量（从0到100）。 </li>\\n<li> <b>!<u>m</u>ode </b> [{mode}] - 设置播放模式。 {mode} 可以使 <i>one-shot</i> （顺序播放）, <i>repeat</i> （循环播放）, <i>random</i> （随机播放）或\\n<i>autoplay</i> （自动播放）四种之一.</li>\\n<li> <b>!duck </b> on/off - 开启或关闭“闪避”功能。开启后，在别人说话时，音乐的音量会自动减小。 </li>\\n<li> <b>!duckv </b> {volume} - 获取或设置“闪避”时的音量。 </li>\\n<li> <b>!<u>duckt</u>hres </b> - 设置“闪避”被激活所需音频信号强度的阈值（默认是3000）。 </li>\\n<li> <b>!<u>o</u>ust </b> - 停止播放，并回到默认频道。 </li>\\n</ul>\\n<b>播放列表</b>\\n\\n<ul>\\n<li> <b>!<u>n</u>ow </b> (或 <b>!np</b>) - 显示当前曲目信息。 </li>\\n<li> <b>!<u>q</u>ueue </b> - 显示播放列表。 </li>\\n<li> <b>!<u>t</u>ag </b> {tags} - 将音乐库中所有包含{tags}标签的曲目添加到播放列表中。 </li>\\n<li> <b>!file </b>(或 <b>!f</b>) {path/folder/keyword} - 添加某一本地音频文件或某个目录中的全部文件到播放列表中。 </li>\\n<li> <b>!<u>filem</u>atch </b>(or <b>!fm</b>) {pattern} - 将文件名满足正则表达式{pattern}的全部文件添加到播放列表中。 </li>\\n<li> <b>!<u>ur</u>l </b> {url} - 添加Youtube或SoundCloud链接。 </li>\\n<li> <b>!<u>playl</u>ist </b> {url} [{offset}] - 添加Youtube或SoundCloud播放列表。 </li>\\n<li> <b>!<u>rad</u>io </b> {url} - 将地址为{url}的电台加入播放列表。 </li>\\n<li> <b>!<u>rbq</u>uery </b> {keyword} - 从http://www.radio-browser.info中搜索某一电台。</li>\\n<li> <b>!<u>rbp</u>lay </b> {id} - 播放ID为{id}的电台 （如 !rbplay 96746）。 </li>\\n<li> <b>!<u>ys</u>earch </b> {keywords} - 搜索Youtube。 使用 <i>!ysearch -n</i> 翻页. </li>\\n<li> <b>!<u>yp</u>lay </b> {keywords} - 搜索Youtube，将第一条搜索结果直接加入播放列表。</li>\\n<li> <b>!<u>sh</u>ortlist </b> (or <b>!sl</b>) {indexes/*} - 添加候选列表中的第{indexes}条曲目（或者是全部曲目，如果该参数为“*”）到播放列表中。 </li>\\n<li> <b>!rm </b> {num} - 删除播放列表上的第{num}首曲目。 </li>\\n<li> <b>!<u>rep</u>eat </b> [{num}] - 重复当前曲目{num}遍（默认重复一遍）。</li>\\n<li> <b>!<u>ran</u>dom </b> - 随机打乱播放列表顺序。</li>\\n</ul>\\n\\n<b>音乐库</b>\\n\\n<ul>\\n<li> <b>!<u>se</u>arch </b> {keywords} - 在音乐库中搜索包含关键词{keywords}的曲目，关键词以空格分割。</li>\\n<li> <b>!<u>li</u>stfile </b> [{pattern}] - 列出路径符合正则表达式{pattern}的文件。 </li>\\n<li> <b>!<u>addt</u>ag </b> [{index}] {tags} - 将标签{tags}添加到第{index}首曲目（如果{index}被省略则默认为当前曲目）。多个标签以“,”分割。 </li>\\n<li> <b>!<u>addt</u>ag </b> * {tags} - 将标签{tags}添加到播放列表上的所有曲目。 </li>\\n<li> <b>!<u>un</u>tag </b> [{index/*}] {tags}/* - 从第{index}首曲目（或当前曲目，若{index}被省略；或全部曲目，若该参数为“*”）上删除标签{tags}（或全部标签）。 </li>\\n<li> <b>!<u>fin</u>dtagged </b> (or <b>!ft</b>) {tags} - 在音乐库中查找包含标签{tags}的曲目。 </li>\\n<li> <b>!<u>del</u>ete </b> {index} - 从音乐库中删除候选列表上的第{index}首曲目。 </li>\\n</ul>\\n\\n<b>其他</b>\\n\\n<ul>\\n<li> <b>!<u>j</u>oinme [{token}] </b> - 加入你所在的频道。</li>\\n<li> <b>!<u>password</u> {password} </b> - 更改你用于访问网页控制界面的密码。</li>\\n</ul>\",\n        \"invalid_index\": \"无效的序号 <i>{index}</i>。 使用 '!queue' 查看播放列表。\",\n        \"last_song_on_the_queue\": \"最后一首。\",\n        \"max_volume\": \"\",\n        \"multiple_file_added\": \"以下曲目已被添加：\",\n        \"multiple_file_deleted\": \"以下曲目已被移出库：\",\n        \"multiple_file_found\": \"搜索到：\",\n        \"multiple_matches\": \"文件未找到！你是不是指：\",\n        \"new_version_found\": \"<h2>发现新版本！</h2> botamusique  {new_version} 可用！ <hr />\\n<h3>更新日志</h3> {changelog} <hr /> 使用 <i>!update</i>自动更新至该版本。\",\n        \"next_to_play\": \"下一首。\",\n        \"no_file\": \"文件未找到。\",\n        \"not_admin\": \"你不是管理员！\",\n        \"not_in_my_channel\": \"你不在我的频道里！\",\n        \"not_playing\": \"无播放中的曲目。\",\n        \"now_playing\": \"正在播放：{item}\",\n        \"page_instruction\": \"第{current}/{total}页。发送<i>!{command} {{page}}</i>翻页。\",\n        \"paused\": \"暂停播放。\",\n        \"playlist_fetching_failed\": \"无法获取播放列表！\",\n        \"pm_not_allowed\": \"不接受私信。\",\n        \"position_in_the_queue\": \"位置：\",\n        \"preconfigurated_radio\": \"预设的电台如下：\",\n        \"queue_contents\": \"播放列表中的曲目：\",\n        \"queue_empty\": \"播放列表为空！\",\n        \"radio\": \"电台\",\n        \"radio_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a>，<i>来自</i> {name}。 <i>由</i> {user} <i>添加</i>。\",\n        \"rb_play_empty\": \"请指定一个电台ID！\",\n        \"rb_query_result\": \"搜索结果如下。发送<i> !rbplay {ID} </i>播放。\",\n        \"records_omitted\": \"……\",\n        \"removed_tags\": \"已将标签 <i>{tags}</i> 从 <b>{song}</b>上移除。\",\n        \"removed_tags_from_all\": \"已将标签 <i>{tags}</i> 从播放列表的曲目中移除。\",\n        \"removing_item\": \"已将 {item} 从播放列表中移除。\",\n        \"repeat\": \"重复{song} {n}次。\",\n        \"report_version\": \"当前的botamusique版本为<b>{version}</b>。\",\n        \"shortlist_instruction\": \"使用<i>!sl {indexes}</i>播放列表中的曲目。\",\n        \"start_updating\": \"开始更新……\",\n        \"stopped\": \"音乐停止。\",\n        \"too_long\": \"<b>{song}</b>超出长度限制（{duration} > {max_duration}）！已被移出播放列表。\",\n        \"unable_download\": \"无法下载<b>{item}</b>。已移出播放列表。\",\n        \"unable_play\": \"无法播放<b>{item}</b>。已移出播放列表。\",\n        \"unknown_mode\": \"未知播放模式\\\"{mode}\\\"。播放模式应为 <i>one-shot</i>, <i>repeat</i>, <i>random</i>中的一个。\",\n        \"update_successful\": \"<h2>botamusique v{version} 安装完毕！</h2><hr />\\n<h3>更新日志</h3> {changelog} <hr /> 请访问我们的 <a href=\\\"https://github.com/azlux/botamusique\\\">github页面</a> 获取更多信息！\",\n        \"url\": \"URL\",\n        \"url_ban\": \"链接{url}被列入黑名单了！\",\n        \"url_ban_list\": \"\",\n        \"url_ban_success\": \"\",\n        \"url_from_playlist\": \"URL\",\n        \"url_from_playlist_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a>，来自播放列表 <a href=\\\"{playlist_url}\\\">{playlist}</a>，由<i> {user} </i>添加。\",\n        \"url_item\": \"<a href=\\\"{url}\\\"><b>{title}</b></a>，<i>由</i> {user} <i>添加</i>。\",\n        \"url_unban_success\": \"\",\n        \"url_unwhitelist_success\": \"\",\n        \"url_whitelist_list\": \"\",\n        \"url_whitelist_success\": \"\",\n        \"user_ban\": \"你被列入黑名单了！无法操作！\",\n        \"user_ban_list\": \"\",\n        \"user_ban_success\": \"\",\n        \"user_password_set\": \"密码已经被更新。\",\n        \"user_unban_success\": \"\",\n        \"web_user_list\": \"下列用户具有访问网络控制界面的权限：<br /> {users}\",\n        \"webpage_address\": \"网页控制界面的地址是<a href=\\\"{address}\\\">{address}</a>。\",\n        \"which_command\": \"你是不是指 <br /> {commands}\",\n        \"wrong_pattern\": \"错误的正则表达式：{error}.\",\n        \"yt_no_more\": \"没有更多条目了！\",\n        \"yt_query_error\": \"无法访问Youtube！\",\n        \"yt_result\": \"Youtube查询结果： {result_table} 使用 <i>!sl {{indexes}}</i> 播放列表中的曲目。 <br />\\n使用<i>!ytquery -n</i>翻页。\"\n    },\n    \"web\": {\n        \"action\": \"操作\",\n        \"add\": \"添加\",\n        \"add_all\": \"添加全部\",\n        \"add_radio\": \"添加电台\",\n        \"add_radio_url\": \"电台URL\",\n        \"add_to_bottom\": \"添加到最后\",\n        \"add_to_bottom_of_current_playlist\": \"添加到播放列表的末尾\",\n        \"add_to_playlist_next\": \"添加到当前曲目的下一首\",\n        \"add_url\": \"添加URL\",\n        \"add_youtube_or_soundcloud_url\": \"添加Youtube或Soundcloud URL\",\n        \"are_you_really_sure\": \"你真的确定吗？\",\n        \"aria_botamusique_logo\": \"Botamusique的Logo\",\n        \"aria_default_cover\": \"默认专辑封面图片：黑色背景上的两个音符。\",\n        \"aria_empty_box\": \"空。\",\n        \"aria_remove_this_song\": \"从当前播放列表中移除该曲目。\",\n        \"aria_skip_current_song\": \"立刻播放该曲目。\",\n        \"aria_skip_to_next_track\": \"播放下一曲目。\",\n        \"aria_spinner\": \"加载中\",\n        \"aria_warning_of_deletion\": \"删除文件警告\",\n        \"autoplay\": \"自动播放\",\n        \"browse_music_file\": \"浏览音乐文件\",\n        \"cancel\": \"取消\",\n        \"cancel_upload_warning\": \"<strong>你真的确定吗？</strong> <br /> 若要取消上传，请再次点击该按钮。\",\n        \"change_playback_mode\": \"更改播放模式\",\n        \"choose_file\": \"选择文件\",\n        \"clear_playlist\": \"清空播放列表\",\n        \"close\": \"关闭\",\n        \"delete_all\": \"删除全部\",\n        \"delete_all_files\": \"删除全部列出的文件\",\n        \"delete_file_warning\": \"全部列出的文件（包括其他页面上的文件）将被从硬盘上删除。你确定要这样做吗？\",\n        \"directory\": \"目录\",\n        \"download_all\": \"下载全部\",\n        \"download_song_from_library\": \"从库中下载曲目文件\",\n        \"edit_submit\": \"编辑！\",\n        \"edit_tags_for\": \"修改标签：\",\n        \"expand_playlist\": \"查看第 <span class=\\\"playlist-expand-item-range\\\"></span> 首曲目。\",\n        \"file\": \"文件\",\n        \"filters\": \"筛选\",\n        \"index\": \"#\",\n        \"keywords\": \"关键词\",\n        \"keywords_placeholder\": \"关键词……\",\n        \"mini_player_title\": \"正在播放……\",\n        \"music_library\": \"音乐库\",\n        \"next_to_play\": \"添加到当前曲目后\",\n        \"no_tag\": \"无标签\",\n        \"oneshot\": \"顺序播放\",\n        \"open_volume_controls\": \"打开音量控制条\",\n        \"page_title\": \"botamusique控制面板\",\n        \"pause\": \"暂停\",\n        \"play\": \"播放\",\n        \"playlist_controls\": \"播放控制\",\n        \"radio\": \"电台\",\n        \"radio_url_placeholder\": \"电台URL……\",\n        \"random\": \"随机播放\",\n        \"remove_song_from_library\": \"从库中移除曲目\",\n        \"repeat\": \"列表循环\",\n        \"rescan_files\": \"重新扫描目录\",\n        \"skip_track\": \"跳过当前曲目\",\n        \"submit\": \"提交\",\n        \"tags\": \"标签\",\n        \"tags_to_add\": \"欲添加的标签\",\n        \"title\": \"标题\",\n        \"token\": \"令牌\",\n        \"token_required\": \"需要登录令牌\",\n        \"token_required_message\": \"你现在在访问{{ name }}的网络控制面板。\\n根据设置，你需要一个令牌才能登录。<br />\\n请发送 \\\"{{ command }}\\\" 以获取你的登录令牌。\",\n        \"type\": \"类型\",\n        \"upload_file\": \"上传音乐文件\",\n        \"upload_submit\": \"上传！\",\n        \"upload_to\": \"上传到\",\n        \"uploaded_finished\": \"上传完毕！\",\n        \"uploading_files\": \"上传中……\",\n        \"url\": \"URL\",\n        \"url_path\": \"URL/路径\",\n        \"url_placeholder\": \"URL……\",\n        \"volume_slider\": \"音量控制条\"\n    }\n}"
  },
  {
    "path": "media/README.md",
    "content": "```\n+----------------------------------------------------------+\n|          <-| URLItem <-- URLFromPlaylistItem             |\n| BaseItem <-| FileItem                                    |\n|          <-| RadioItem                                   |\n++---------------------------------------------------------+\n```\n"
  },
  {
    "path": "media/__init__.py",
    "content": ""
  },
  {
    "path": "media/cache.py",
    "content": "import logging\nimport os\n\nimport json\nimport threading\n\nfrom media.item import item_builders, item_id_generators, dict_to_item\nimport media.file\nimport media.url\nimport media.url_from_playlist\nimport media.radio\nfrom database import MusicDatabase, Condition\nimport variables as var\nimport util\n\n\nclass ItemNotCachedError(Exception):\n    pass\n\n\nclass MusicCache(dict):\n    def __init__(self, db: MusicDatabase):\n        super().__init__()\n        self.db = db\n        self.log = logging.getLogger(\"bot\")\n        self.dir_lock = threading.Lock()\n\n    def get_item_by_id(self, id):\n        if id in self:\n            return self[id]\n\n        # if not cached, query the database\n        item = self.fetch(id)\n        if item is not None:\n            self[id] = item\n            self.log.debug(\"library: music found in database: %s\" % item.format_debug_string())\n            return item\n        else:\n            return None\n            # print(id)\n            # raise KeyError(\"Unable to fetch item from the database! Please try to refresh the cache by !recache.\")\n\n    def get_item(self, **kwargs):\n        # kwargs should provide type and id, and parameters to build the item if not existed in the library.\n        # if cached\n        if 'id' in kwargs:\n            id = kwargs['id']\n        else:\n            id = item_id_generators[kwargs['type']](**kwargs)\n\n        if id in self:\n            return self[id]\n\n        # if not cached, query the database\n        item = self.fetch(id)\n        if item is not None:\n            self[id] = item\n            self.log.debug(\"library: music found in database: %s\" % item.format_debug_string())\n            return item\n\n        # if not in the database, build one\n        self[id] = item_builders[kwargs['type']](**kwargs)  # newly built item will not be saved immediately\n        return self[id]\n\n    def get_items_by_tags(self, tags):\n        music_dicts = self.db.query_music_by_tags(tags)\n        items = []\n        if music_dicts:\n            for music_dict in music_dicts:\n                id = music_dict['id']\n                self[id] = dict_to_item(music_dict)\n                items.append(self[id])\n\n        return items\n\n    def fetch(self, id):\n        music_dict = self.db.query_music_by_id(id)\n        if music_dict:\n            self[id] = dict_to_item(music_dict)\n            return self[id]\n        else:\n            return None\n\n    def save(self, id):\n        self.log.debug(\"library: music save into database: %s\" % self[id].format_debug_string())\n        self.db.insert_music(self[id].to_dict())\n        self.db.manage_special_tags()\n\n    def free_and_delete(self, id):\n        item = self.get_item_by_id(id)\n        if item:\n            self.log.debug(\"library: DELETE item from the database: %s\" % item.format_debug_string())\n\n            if item.type == 'url':\n                if os.path.exists(item.path):\n                    os.remove(item.path)\n\n            if item.id in self:\n                del self[item.id]\n            self.db.delete_music(Condition().and_equal(\"id\", item.id))\n\n    def free(self, id):\n        if id in self:\n            self.log.debug(\"library: cache freed for item: %s\" % self[id].format_debug_string())\n            del self[id]\n\n    def free_all(self):\n        self.log.debug(\"library: all cache freed\")\n        self.clear()\n\n    def build_dir_cache(self):\n        self.dir_lock.acquire()\n        self.log.info(\"library: rebuild directory cache\")\n        files = util.get_recursive_file_list_sorted(var.music_folder)\n\n        # remove deleted files\n        results = self.db.query_music(Condition().or_equal('type', 'file'))\n        for result in results:\n            if result['path'] not in files:\n                self.log.debug(\"library: music file missed: %s, delete from library.\" % result['path'])\n                self.db.delete_music(Condition().and_equal('id', result['id']))\n            else:\n                files.remove(result['path'])\n\n        for file in files:\n            results = self.db.query_music(Condition().and_equal('path', file))\n            if not results:\n                item = item_builders['file'](path=file)\n                self.log.debug(\"library: music save into database: %s\" % item.format_debug_string())\n                self.db.insert_music(item.to_dict())\n\n        self.db.manage_special_tags()\n        self.dir_lock.release()\n\n\nclass CachedItemWrapper:\n    def __init__(self, lib, id, type, user):\n        self.lib = lib\n        self.id = id\n        self.user = user\n        self.type = type\n        self.log = logging.getLogger(\"bot\")\n        self.version = 0\n\n    def item(self):\n        if self.id in self.lib:\n            return self.lib[self.id]\n        else:\n            raise ItemNotCachedError(f\"Uncached item of id {self.id}, type {self.type}.\")\n\n    def to_dict(self):\n        dict = self.item().to_dict()\n        dict['user'] = self.user\n        return dict\n\n    def validate(self):\n        ret = self.item().validate()\n        if ret and self.item().version > self.version:\n            self.version = self.item().version\n            self.lib.save(self.id)\n        return ret\n\n    def prepare(self):\n        ret = self.item().prepare()\n        if ret and self.item().version > self.version:\n            self.version = self.item().version\n            self.lib.save(self.id)\n        return ret\n\n    def uri(self):\n        return self.item().uri()\n\n    def add_tags(self, tags):\n        self.item().add_tags(tags)\n        if self.item().version > self.version:\n            self.version = self.item().version\n            self.lib.save(self.id)\n\n    def remove_tags(self, tags):\n        self.item().remove_tags(tags)\n        if self.item().version > self.version:\n            self.version = self.item().version\n            self.lib.save(self.id)\n\n    def clear_tags(self):\n        self.item().clear_tags()\n        if self.item().version > self.version:\n            self.version = self.item().version\n            self.lib.save(self.id)\n\n    def is_ready(self):\n        return self.item().is_ready()\n\n    def is_failed(self):\n        return self.item().is_failed()\n\n    def format_current_playing(self):\n        return self.item().format_current_playing(self.user)\n\n    def format_song_string(self):\n        return self.item().format_song_string(self.user)\n\n    def format_title(self):\n        return self.item().format_title()\n\n    def format_debug_string(self):\n        return self.item().format_debug_string()\n\n    def display_type(self):\n        return self.item().display_type()\n\n\n# Remember!!! Get wrapper functions will automatically add items into the cache!\ndef get_cached_wrapper(item, user):\n    if item:\n        var.cache[item.id] = item\n        return CachedItemWrapper(var.cache, item.id, item.type, user)\n    return None\n\ndef get_cached_wrappers(items, user):\n    wrappers = []\n    for item in items:\n        if item:\n            wrappers.append(get_cached_wrapper(item, user))\n\n    return wrappers\n\ndef get_cached_wrapper_from_scrap(**kwargs):\n    item = var.cache.get_item(**kwargs)\n    if 'user' not in kwargs:\n        raise KeyError(\"Which user added this song?\")\n    return CachedItemWrapper(var.cache, item.id, kwargs['type'], kwargs['user'])\n\ndef get_cached_wrapper_from_dict(dict_from_db, user):\n    if dict_from_db:\n        item = dict_to_item(dict_from_db)\n        return get_cached_wrapper(item, user)\n    return None\n\ndef get_cached_wrappers_from_dicts(dicts_from_db, user):\n    items = []\n    for dict_from_db in dicts_from_db:\n        if dict_from_db:\n            items.append(get_cached_wrapper_from_dict(dict_from_db, user))\n\n    return items\n\ndef get_cached_wrapper_by_id(id, user):\n    item = var.cache.get_item_by_id(id)\n    if item:\n        return CachedItemWrapper(var.cache, item.id, item.type, user)\n\ndef get_cached_wrappers_by_tags(tags, user):\n    items = var.cache.get_items_by_tags(tags)\n    ret = []\n    for item in items:\n        ret.append(CachedItemWrapper(var.cache, item.id, item.type, user))\n    return ret\n"
  },
  {
    "path": "media/file.py",
    "content": "import os\nimport re\nfrom io import BytesIO\nimport base64\nimport hashlib\nimport mutagen\nfrom PIL import Image\n\nimport util\nimport variables as var\nfrom media.item import BaseItem, item_builders, item_loaders, item_id_generators, ValidationFailedError\nfrom constants import tr_cli as tr\n\n'''\ntype : file\n    id\n    path\n    title\n    artist\n    duration\n    thumbnail\n    user\n'''\n\n\ndef file_item_builder(**kwargs):\n    return FileItem(kwargs['path'])\n\n\ndef file_item_loader(_dict):\n    return FileItem(\"\", _dict)\n\n\ndef file_item_id_generator(**kwargs):\n    return hashlib.md5(kwargs['path'].encode()).hexdigest()\n\n\nitem_builders['file'] = file_item_builder\nitem_loaders['file'] = file_item_loader\nitem_id_generators['file'] = file_item_id_generator\n\n\nclass FileItem(BaseItem):\n    def __init__(self, path, from_dict=None):\n        if not from_dict:\n            super().__init__()\n            self.path = path\n            self.title = \"\"\n            self.artist = \"\"\n            self.thumbnail = None\n            self.id = hashlib.md5(path.encode()).hexdigest()\n            if os.path.exists(self.uri()):\n                self._get_info_from_tag()\n                self.ready = \"yes\"\n                self.duration = util.get_media_duration(self.uri())\n            self.keywords = self.title + \" \" + self.artist\n        else:\n            super().__init__(from_dict)\n            self.artist = from_dict['artist']\n            self.thumbnail = from_dict['thumbnail']\n            try:\n                self.validate()\n            except ValidationFailedError:\n                self.ready = \"failed\"\n\n        self.type = \"file\"\n\n    def uri(self):\n        return var.music_folder + self.path if self.path[0] != \"/\" else self.path\n\n    def is_ready(self):\n        return True\n\n    def validate(self):\n        if not os.path.exists(self.uri()):\n            self.log.info(\n                \"file: music file missed for %s\" % self.format_debug_string())\n            raise ValidationFailedError(tr('file_missed', file=self.path))\n\n        if self.duration == 0:\n            self.duration = util.get_media_duration(self.uri())\n            self.version += 1  # 0 -> 1, notify the wrapper to save me\n        self.ready = \"yes\"\n        return True\n\n    def _get_info_from_tag(self):\n        path, file_name_ext = os.path.split(self.uri())\n        file_name, ext = os.path.splitext(file_name_ext)\n\n        assert path is not None and file_name is not None\n\n        try:\n            im = None\n            path_thumbnail = os.path.join(path, file_name + \".jpg\")\n\n            if os.path.isfile(path_thumbnail):\n                im = Image.open(path_thumbnail)\n            else:\n                path_thumbnail = os.path.join(path, \"cover.jpg\")\n                if os.path.isfile(path_thumbnail):\n                    im = Image.open(path_thumbnail)\n\n            if ext == \".mp3\":\n                # title: TIT2\n                # artist: TPE1, TPE2\n                # album: TALB\n                # cover artwork: APIC:\n                tags = mutagen.File(self.uri())\n                if 'TIT2' in tags:\n                    self.title = tags['TIT2'].text[0]\n                if 'TPE1' in tags:  # artist\n                    self.artist = tags['TPE1'].text[0]\n\n                if im is None:\n                    if \"APIC:\" in tags:\n                        im = Image.open(BytesIO(tags[\"APIC:\"].data))\n\n            elif ext == \".m4a\" or ext == \".m4b\" or ext == \".mp4\" or ext == \".m4p\":\n                # title: ©nam (\\xa9nam)\n                # artist: ©ART\n                # album: ©alb\n                # cover artwork: covr\n                tags = mutagen.File(self.uri())\n                if '©nam' in tags:\n                    self.title = tags['©nam'][0]\n                if '©ART' in tags:  # artist\n                    self.artist = tags['©ART'][0]\n\n                if im is None:\n                    if \"covr\" in tags:\n                        im = Image.open(BytesIO(tags[\"covr\"][0]))\n\n            elif ext == \".opus\":\n                # title: 'title'\n                # artist: 'artist'\n                # album: 'album'\n                # cover artwork: 'metadata_block_picture', and then:\n                ##                          |\n                ##                          |\n                ##                          v\n                ##            Decode string as base64 binary\n                ##                          |\n                ##                          v\n                ##      Open that binary as a mutagen.flac.Picture\n                ##                          |\n                ##                          v\n                ##              Extract binary image data\n                tags = mutagen.File(self.uri())\n                if 'title' in tags:\n                    self.title = tags['title'][0]\n                if 'artist' in tags:\n                    self.artist = tags['artist'][0]\n\n                if im is None:\n                    if 'metadata_block_picture' in tags:\n                        pic_as_base64 = tags['metadata_block_picture'][0]\n                        as_flac_picture = mutagen.flac.Picture(base64.b64decode(pic_as_base64))\n                        im = Image.open(BytesIO(as_flac_picture.data))\n\n            elif ext == \".flac\":\n                # title: 'title'\n                # artist: 'artist'\n                # album: 'album'\n                # cover artwork: tags.pictures\n                tags = mutagen.File(self.uri())\n                if 'title' in tags:\n                    self.title = tags['title'][0]\n                if 'artist' in tags:\n                    self.artist = tags['artist'][0]\n\n                if im is None:\n                    for flac_picture in tags.pictures:\n                        if flac_picture.type == 3:\n                            im = Image.open(BytesIO(flac_picture.data))\n\n            if im:\n                self.thumbnail = self._prepare_thumbnail(im)\n        except:\n            pass\n\n        if not self.title:\n            self.title = file_name\n\n    @staticmethod\n    def _prepare_thumbnail(im):\n        im.thumbnail((100, 100), Image.LANCZOS)\n        buffer = BytesIO()\n        im = im.convert('RGB')\n        im.save(buffer, format=\"JPEG\")\n        return base64.b64encode(buffer.getvalue()).decode('utf-8')\n\n    def to_dict(self):\n        dict = super().to_dict()\n        dict['type'] = 'file'\n        dict['path'] = self.path\n        dict['title'] = self.title\n        dict['artist'] = self.artist\n        dict['thumbnail'] = self.thumbnail\n        return dict\n\n    def format_debug_string(self):\n        return \"[file] {descrip} ({path})\".format(\n            descrip=self.format_title(),\n            path=self.path\n        )\n\n    def format_song_string(self, user):\n        return tr(\"file_item\",\n                  title=self.title,\n                  artist=self.artist if self.artist else '??',\n                  user=user\n                  )\n\n    def format_current_playing(self, user):\n        display = tr(\"now_playing\", item=self.format_song_string(user))\n        if self.thumbnail:\n            thumbnail_html = '<img width=\"80\" src=\"data:image/jpge;base64,' + \\\n                             self.thumbnail + '\"/>'\n            display += \"<br />\" + thumbnail_html\n\n        return display\n\n    def format_title(self):\n        title = self.title if self.title else self.path\n        if self.artist:\n            return self.artist + \" - \" + title\n        else:\n            return title\n\n    def display_type(self):\n        return tr(\"file\")\n"
  },
  {
    "path": "media/item.py",
    "content": "import logging\n\nitem_builders = {}\nitem_loaders = {}\nitem_id_generators = {}\n\n\ndef example_builder(**kwargs):\n    return BaseItem()\n\n\ndef example_loader(_dict):\n    return BaseItem(from_dict=_dict)\n\n\ndef example_id_generator(**kwargs):\n    return \"\"\n\n\nitem_builders['base'] = example_builder\nitem_loaders['base'] = example_loader\nitem_id_generators['base'] = example_id_generator\n\n\ndef dicts_to_items(music_dicts):\n    items = []\n    for music_dict in music_dicts:\n        type = music_dict['type']\n        items.append(item_loaders[type](music_dict))\n    return items\n\n\ndef dict_to_item(music_dict):\n    type = music_dict['type']\n    return item_loaders[type](music_dict)\n\nclass ValidationFailedError(Exception):\n    def __init__(self, msg = None):\n        self.msg = msg\n\nclass PreparationFailedError(Exception):\n    def __init__(self, msg = None):\n        self.msg = msg\n\nclass BaseItem:\n    def __init__(self, from_dict=None):\n        self.log = logging.getLogger(\"bot\")\n        self.type = \"base\"\n        self.title = \"\"\n        self.path = \"\"\n        self.tags = []\n        self.keywords = \"\"\n        self.duration = 0\n        self.version = 0  # if version increase, wrapper will re-save this item\n\n        if from_dict is None:\n            self.id = \"\"\n            self.ready = \"pending\"  # pending - is_valid() -> validated - prepare() -> yes, failed\n        else:\n            self.id = from_dict['id']\n            self.ready = from_dict['ready']\n            self.tags = from_dict['tags']\n            self.title = from_dict['title']\n            self.path = from_dict['path']\n            self.keywords = from_dict['keywords']\n            self.duration = from_dict['duration']\n\n    def is_ready(self):\n        return True if self.ready == \"yes\" else False\n\n    def is_failed(self):\n        return True if self.ready == \"failed\" else False\n\n    def validate(self):\n        raise ValidationFailedError(None)\n\n    def uri(self):\n        raise\n\n    def prepare(self):\n        return True\n\n    def add_tags(self, tags):\n        for tag in tags:\n            if tag and tag not in self.tags:\n                self.tags.append(tag)\n                self.version += 1\n\n    def remove_tags(self, tags):\n        for tag in tags:\n            if tag in self.tags:\n                self.tags.remove(tag)\n                self.version += 1\n\n    def clear_tags(self):\n        if len(self.tags) > 0:\n            self.tags = []\n            self.version += 1\n\n    def format_song_string(self, user):\n        return self.id\n\n    def format_current_playing(self, user):\n        return self.id\n\n    def format_title(self):\n        return self.title\n\n    def format_debug_string(self):\n        return self.id\n\n    def display_type(self):\n        return \"\"\n\n    def to_dict(self):\n        return {\"type\": self.type,\n                \"id\": self.id,\n                \"ready\": self.ready,\n                \"title\": self.title,\n                \"path\": self.path,\n                \"tags\": self.tags,\n                \"keywords\": self.keywords,\n                \"duration\": self.duration}\n"
  },
  {
    "path": "media/playlist.py",
    "content": "import json\nimport threading\nimport logging\nimport random\nimport time\n\nimport variables as var\nfrom media.cache import (CachedItemWrapper, ItemNotCachedError,\n                         get_cached_wrapper_from_dict, get_cached_wrapper_by_id)\nfrom database import Condition\nfrom media.item import ValidationFailedError, PreparationFailedError\n\n\ndef get_playlist(mode, _list=None, _index=None):\n    index = -1\n    if _list and _index is None:\n        index = _list.current_index\n\n    if _list is None:\n        if mode == \"one-shot\":\n            return OneshotPlaylist()\n        elif mode == \"repeat\":\n            return RepeatPlaylist()\n        elif mode == \"random\":\n            return RandomPlaylist()\n        elif mode == \"autoplay\":\n            return AutoPlaylist()\n    else:\n        if mode == \"one-shot\":\n            return OneshotPlaylist().from_list(_list, index)\n        elif mode == \"repeat\":\n            return RepeatPlaylist().from_list(_list, index)\n        elif mode == \"random\":\n            return RandomPlaylist().from_list(_list, index)\n        elif mode == \"autoplay\":\n            return AutoPlaylist().from_list(_list, index)\n    raise\n\n\nclass BasePlaylist(list):\n    def __init__(self):\n        super().__init__()\n        self.current_index = -1\n        self.version = 0  # increase by one after each change\n        self.mode = \"base\"  # \"repeat\", \"random\"\n        self.pending_items = []\n        self.log = logging.getLogger(\"bot\")\n        self.validating_thread_lock = threading.Lock()\n        self.playlist_lock = threading.RLock()\n\n    def is_empty(self):\n        return True if len(self) == 0 else False\n\n    def from_list(self, _list, current_index):\n        self.version += 1\n        super().clear()\n        self.extend(_list)\n        self.current_index = current_index\n\n        return self\n\n    def append(self, item: CachedItemWrapper):\n        with self.playlist_lock:\n            self.version += 1\n            super().append(item)\n            self.pending_items.append(item)\n\n        self.async_validate()\n\n        return item\n\n    def insert(self, index, item):\n        with self.playlist_lock:\n            self.version += 1\n            if index == -1:\n                index = self.current_index\n\n            super().insert(index, item)\n\n            if index <= self.current_index:\n                self.current_index += 1\n\n            self.pending_items.append(item)\n\n        self.async_validate()\n\n        return item\n\n    def extend(self, items):\n        with self.playlist_lock:\n            self.version += 1\n            super().extend(items)\n            self.pending_items.extend(items)\n\n        self.async_validate()\n        return items\n\n    def next(self):\n        with self.playlist_lock:\n            if len(self) == 0:\n                return False\n\n            if self.current_index < len(self) - 1:\n                self.current_index += 1\n                return self[self.current_index]\n            else:\n                return False\n\n    def point_to(self, index):\n        with self.playlist_lock:\n            if -1 <= index < len(self):\n                self.current_index = index\n\n    def find(self, id):\n        with self.playlist_lock:\n            for index, wrapper in enumerate(self):\n                if wrapper.item.id == id:\n                    return index\n        return None\n\n    def __delitem__(self, key):\n        return self.remove(key)\n\n    def remove(self, index):\n        with self.playlist_lock:\n            self.version += 1\n            if index > len(self) - 1:\n                return False\n\n            removed = self[index]\n            super().__delitem__(index)\n\n            if self.current_index > index:\n                self.current_index -= 1\n\n        # reference counter\n        counter = 0\n        for wrapper in self:\n            if wrapper.id == removed.id:\n                counter += 1\n\n        if counter == 0:\n            var.cache.free(removed.id)\n        return removed\n\n    def remove_by_id(self, id):\n        to_be_removed = []\n        for index, wrapper in enumerate(self):\n            if wrapper.id == id:\n                to_be_removed.append(index)\n\n        if to_be_removed:\n            self.version += 1\n\n        for index in to_be_removed:\n            self.remove(index)\n\n    def current_item(self):\n        with self.playlist_lock:\n            if len(self) == 0:\n                return False\n\n            return self[self.current_index]\n\n    def next_index(self):\n        with self.playlist_lock:\n            if self.current_index < len(self) - 1:\n                return self.current_index + 1\n\n        return False\n\n    def next_item(self):\n        with self.playlist_lock:\n            if self.current_index < len(self) - 1:\n                return self[self.current_index + 1]\n\n        return False\n\n    def randomize(self):\n        with self.playlist_lock:\n            # current_index will lose track after shuffling, thus we take current music out before shuffling\n            # current = self.current_item()\n            # del self[self.current_index]\n\n            random.shuffle(self)\n\n            # self.insert(0, current)\n            self.current_index = -1\n            self.version += 1\n\n    def clear(self):\n        with self.playlist_lock:\n            self.version += 1\n            self.current_index = -1\n            super().clear()\n\n        var.cache.free_all()\n\n    def save(self):\n        with self.playlist_lock:\n            var.db.remove_section(\"playlist_item\")\n            assert self.current_index is not None\n            var.db.set(\"playlist\", \"current_index\", self.current_index)\n\n            for index, music in enumerate(self):\n                var.db.set(\"playlist_item\", str(index), json.dumps({'id': music.id, 'user': music.user}))\n\n    def load(self):\n        current_index = var.db.getint(\"playlist\", \"current_index\", fallback=-1)\n        if current_index == -1:\n            return\n\n        items = var.db.items(\"playlist_item\")\n        if items:\n            music_wrappers = []\n            items.sort(key=lambda v: int(v[0]))\n            for item in items:\n                item = json.loads(item[1])\n                music_wrapper = get_cached_wrapper_by_id(item['id'], item['user'])\n                if music_wrapper:\n                    music_wrappers.append(music_wrapper)\n            self.from_list(music_wrappers, current_index)\n\n    def _debug_print(self):\n        print(\"===== Playlist(%d) =====\" % self.current_index)\n        for index, item_wrapper in enumerate(self):\n            if index == self.current_index:\n                print(\"-> %d %s\" % (index, item_wrapper.format_debug_string()))\n            else:\n                print(\"%d %s\" % (index, item_wrapper.format_debug_string()))\n        print(\"=====     End     =====\")\n\n    def async_validate(self):\n        if not self.validating_thread_lock.locked():\n            time.sleep(0.1)  # Just avoid validation finishes too fast and delete songs while something is reading it.\n            th = threading.Thread(target=self._check_valid, name=\"Validating\")\n            th.daemon = True\n            th.start()\n\n    def _check_valid(self):\n        self.log.debug(\"playlist: start validating...\")\n        self.validating_thread_lock.acquire()\n        while len(self.pending_items) > 0:\n            item = self.pending_items.pop()\n            try:\n                item.item()\n            except ItemNotCachedError:\n                # In some very subtle case, items are removed and freed from\n                # the playlist and the cache, before validation even starts,\n                # causes, freed items remain in pending_items.\n                # Simply ignore these items here.\n                continue\n\n            self.log.debug(\"playlist: validating %s\" % item.format_debug_string())\n            ver = item.version\n\n            try:\n                item.validate()\n            except ValidationFailedError as e:\n                self.log.debug(\"playlist: validating failed.\")\n                if var.bot:\n                    var.bot.send_channel_msg(e.msg)\n                self.remove_by_id(item.id)\n                var.cache.free_and_delete(item.id)\n                continue\n\n            if item.version > ver:\n                self.version += 1\n\n        self.log.debug(\"playlist: validating finished.\")\n        self.validating_thread_lock.release()\n\n\nclass OneshotPlaylist(BasePlaylist):\n    def __init__(self):\n        super().__init__()\n        self.mode = \"one-shot\"\n        self.current_index = -1\n\n    def current_item(self):\n        with self.playlist_lock:\n            if len(self) == 0:\n                self.current_index = -1\n                return False\n\n            if self.current_index == -1:\n                self.current_index = 0\n\n            return self[self.current_index]\n\n    def from_list(self, _list, current_index):\n        with self.playlist_lock:\n            if len(_list) > 0:\n                if current_index > -1:\n                    for i in range(current_index):\n                        _list.pop(0)\n                    return super().from_list(_list, 0)\n                return super().from_list(_list, -1)\n            return self\n\n    def next(self):\n        with self.playlist_lock:\n            if len(self) > 0:\n                self.version += 1\n\n                if self.current_index != -1:\n                    super().__delitem__(self.current_index)\n                    if len(self) == 0:\n                        return False\n                else:\n                    self.current_index = 0\n\n                return self[0]\n            else:\n                self.current_index = -1\n                return False\n\n    def next_index(self):\n        if len(self) > 1:\n            return 1\n        else:\n            return False\n\n    def next_item(self):\n        if len(self) > 1:\n            return self[1]\n        else:\n            return False\n\n    def point_to(self, index):\n        with self.playlist_lock:\n            self.version += 1\n            self.current_index = -1\n            for i in range(index):\n                super().__delitem__(0)\n\n\nclass RepeatPlaylist(BasePlaylist):\n    def __init__(self):\n        super().__init__()\n        self.mode = \"repeat\"\n\n    def next(self):\n        with self.playlist_lock:\n            if len(self) == 0:\n                return False\n\n            if self.current_index < len(self) - 1:\n                self.current_index += 1\n                return self[self.current_index]\n            else:\n                self.current_index = 0\n                return self[0]\n\n    def next_index(self):\n        with self.playlist_lock:\n            if self.current_index < len(self) - 1:\n                return self.current_index + 1\n            else:\n                return 0\n\n    def next_item(self):\n        if len(self) == 0:\n            return False\n        return self[self.next_index()]\n\n\nclass RandomPlaylist(BasePlaylist):\n    def __init__(self):\n        super().__init__()\n        self.mode = \"random\"\n\n    def from_list(self, _list, current_index):\n        self.version += 1\n        random.shuffle(_list)\n        return super().from_list(_list, -1)\n\n    def next(self):\n        with self.playlist_lock:\n            if len(self) == 0:\n                return False\n\n            if self.current_index < len(self) - 1:\n                self.current_index += 1\n                return self[self.current_index]\n            else:\n                self.version += 1\n                self.randomize()\n                self.current_index = 0\n                return self[0]\n\n\nclass AutoPlaylist(OneshotPlaylist):\n    def __init__(self):\n        super().__init__()\n        self.mode = \"autoplay\"\n\n    def refresh(self):\n        dicts = var.music_db.query_random_music(var.config.getint(\"bot\", \"autoplay_length\"),\n                                                Condition().and_not_sub_condition(\n                                                    Condition().and_like('tags', \"%don't autoplay,%\")))\n\n        if dicts:\n            _list = [get_cached_wrapper_from_dict(_dict, \"AutoPlay\") for _dict in dicts]\n            self.from_list(_list, -1)\n\n    # def from_list(self, _list, current_index):\n    #     self.version += 1\n    #     self.refresh()\n    #     return self\n\n    def clear(self):\n        super().clear()\n        self.refresh()\n\n    def next(self):\n        if len(self) == 0:\n            self.refresh()\n        return super().next()\n"
  },
  {
    "path": "media/radio.py",
    "content": "import re\nimport logging\nimport struct\nimport requests\nimport traceback\nimport hashlib\n\nfrom media.item import BaseItem\nfrom media.item import item_builders, item_loaders, item_id_generators\nfrom constants import tr_cli as tr\n\nlog = logging.getLogger(\"bot\")\n\n\ndef get_radio_server_description(url):\n    global log\n\n    log.debug(\"radio: fetching radio server description\")\n    p = re.compile('(https?://[^/]*)', re.IGNORECASE)\n    res = re.search(p, url)\n    base_url = res.group(1)\n    url_icecast = base_url + '/status-json.xsl'\n    url_shoutcast = base_url + '/stats?json=1'\n    try:\n        response = requests.head(url_shoutcast, timeout=3)\n        if not response.headers.get('content-type', '').startswith((\"audio/\", \"video/\")):\n            response = requests.get(url_shoutcast, timeout=10)\n            data = response.json()\n            title_server = data['servertitle']\n            return title_server\n            # logging.info(\"TITLE FOUND SHOUTCAST: \" + title_server)\n    except (requests.exceptions.ConnectionError,\n            requests.exceptions.HTTPError,\n            requests.exceptions.ReadTimeout,\n            requests.exceptions.Timeout):\n        error_traceback = traceback.format_exc()\n        error = error_traceback.rstrip().split(\"\\n\")[-1]\n        log.debug(\"radio: unsuccessful attempts on fetching radio description (shoutcast): \" + error)\n    except ValueError:\n        return url\n\n    try:\n        response = requests.head(url_shoutcast, timeout=3)\n        if not response.headers.get('content-type', '').startswith((\"audio/\", \"video/\")):\n            response = requests.get(url_icecast, timeout=10)\n            data = response.json()\n            source = data['icestats']['source']\n            if type(source) is list:\n                source = source[0]\n            title_server = source['server_name']\n            if 'server_description' in source:\n                title_server += ' - ' + source['server_description']\n            # logging.info(\"TITLE FOUND ICECAST: \" + title_server)\n            return title_server\n    except (requests.exceptions.ConnectionError,\n            requests.exceptions.HTTPError,\n            requests.exceptions.ReadTimeout,\n            requests.exceptions.Timeout):\n        error_traceback = traceback.format_exc()\n        error = error_traceback.rstrip().split(\"\\n\")[-1]\n        log.debug(\"radio: unsuccessful attempts on fetching radio description (icecast): \" + error)\n\n    return url\n\n\ndef get_radio_title(url):\n    global log\n\n    log.debug(\"radio: fetching radio server description\")\n    try:\n        r = requests.get(url, headers={'Icy-MetaData': '1'}, stream=True, timeout=10)\n        icy_metaint_header = int(r.headers['icy-metaint'])\n        r.raw.read(icy_metaint_header)\n\n        metadata_length = struct.unpack('B', r.raw.read(1))[0] * 16  # length byte\n        metadata = r.raw.read(metadata_length).rstrip(b'\\0')\n        logging.info(metadata)\n        # extract title from the metadata\n        m = re.search(br\"StreamTitle='([^']*)';\", metadata)\n        if m:\n            title = m.group(1)\n            if title:\n                return title.decode()\n    except (requests.exceptions.ConnectionError,\n            requests.exceptions.HTTPError,\n            requests.exceptions.ReadTimeout,\n            requests.exceptions.Timeout,\n            KeyError):\n        log.debug(\"radio: unsuccessful attempts on fetching radio title (icy)\")\n    return url\n\n\ndef radio_item_builder(**kwargs):\n    if 'name' in kwargs:\n        return RadioItem(kwargs['url'], kwargs['name'])\n    else:\n        return RadioItem(kwargs['url'], '')\n\n\ndef radio_item_loader(_dict):\n    return RadioItem(\"\", \"\", _dict)\n\n\ndef radio_item_id_generator(**kwargs):\n    return hashlib.md5(kwargs['url'].encode()).hexdigest()\n\n\nitem_builders['radio'] = radio_item_builder\nitem_loaders['radio'] = radio_item_loader\nitem_id_generators['radio'] = radio_item_id_generator\n\n\nclass RadioItem(BaseItem):\n    def __init__(self, url, name=\"\", from_dict=None):\n        if from_dict is None:\n            super().__init__()\n            self.url = url\n            if not name:\n                self.title = get_radio_server_description(self.url)  # The title of the radio station\n            else:\n                self.title = name\n            self.id = hashlib.md5(url.encode()).hexdigest()\n        else:\n            super().__init__(from_dict)\n            self.url = from_dict['url']\n            self.title = from_dict['title']\n\n        self.type = \"radio\"\n\n    def validate(self):\n        self.version += 1  # 0 -> 1, notify the wrapper to save me when validate() is visited the first time\n        return True\n\n    def is_ready(self):\n        return True\n\n    def uri(self):\n        return self.url\n\n    def to_dict(self):\n        dict = super().to_dict()\n        dict['url'] = self.url\n        dict['title'] = self.title\n\n        return dict\n\n    def format_debug_string(self):\n        return \"[radio] {name} ({url})\".format(\n            name=self.title,\n            url=self.url\n        )\n\n    def format_song_string(self, user):\n        return tr(\"radio_item\",\n                  url=self.url,\n                  title=get_radio_title(self.url),  # the title of current song\n                  name=self.title,  # the title of radio station\n                  user=user\n                  )\n\n    def format_current_playing(self, user):\n        return tr(\"now_playing\", item=self.format_song_string(user))\n\n    def format_title(self):\n        return self.title if self.title else self.url\n\n    def display_type(self):\n        return tr(\"radio\")\n"
  },
  {
    "path": "media/url.py",
    "content": "import threading\nimport logging\nimport os\nimport hashlib\nimport traceback\nfrom PIL import Image\nimport yt_dlp as youtube_dl\nimport glob\nfrom io import BytesIO\nimport base64\n\nimport util\nfrom constants import tr_cli as tr\nimport media\nimport variables as var\nfrom media.item import BaseItem, item_builders, item_loaders, item_id_generators, ValidationFailedError, \\\n    PreparationFailedError\nfrom util import format_time\n\nlog = logging.getLogger(\"bot\")\n\n\ndef url_item_builder(**kwargs):\n    return URLItem(kwargs['url'])\n\n\ndef url_item_loader(_dict):\n    return URLItem(\"\", _dict)\n\n\ndef url_item_id_generator(**kwargs):\n    return hashlib.md5(kwargs['url'].encode()).hexdigest()\n\n\nitem_builders['url'] = url_item_builder\nitem_loaders['url'] = url_item_loader\nitem_id_generators['url'] = url_item_id_generator\n\n\nclass URLItem(BaseItem):\n    def __init__(self, url, from_dict=None):\n        self.validating_lock = threading.Lock()\n        if from_dict is None:\n            super().__init__()\n            self.url = url if url[-1] != \"/\" else url[:-1]\n            self.title = \"\"\n            self.duration = 0\n            self.id = hashlib.md5(url.encode()).hexdigest()\n            self.path = var.tmp_folder + self.id\n            self.thumbnail = \"\"\n            self.keywords = \"\"\n        else:\n            super().__init__(from_dict)\n            self.url = from_dict['url']\n            self.duration = from_dict['duration']\n            self.path = from_dict['path']\n            self.title = from_dict['title']\n            self.thumbnail = from_dict['thumbnail']\n\n        self.downloading = False\n        self.type = \"url\"\n\n    def uri(self):\n        return self.path\n\n    def is_ready(self):\n        if self.downloading or self.ready != 'yes':\n            return False\n        if self.ready == 'yes' and not os.path.exists(self.path):\n            self.log.info(\n                \"url: music file missed for %s\" % self.format_debug_string())\n            self.ready = 'validated'\n            return False\n\n        return True\n\n    def validate(self):\n        try:\n            self.validating_lock.acquire()\n            if self.ready in ['yes', 'validated']:\n                return True\n\n            # if self.ready == 'failed':\n            #     self.validating_lock.release()\n            #     return False\n            #\n            if os.path.exists(self.path):\n                self.ready = \"yes\"\n                return True\n\n            # Check if this url is banned\n            if var.db.has_option('url_ban', self.url):\n                raise ValidationFailedError(tr('url_ban', url=self.url))\n\n            # avoid multiple process validating in the meantime\n            info = self._get_info_from_url()\n\n            if not info:\n                return False\n\n            # Check if the song is too long and is not whitelisted\n            max_duration = var.config.getint('bot', 'max_track_duration') * 60\n            if max_duration and \\\n                    not var.db.has_option('url_whitelist', self.url) and \\\n                    self.duration > max_duration:\n                log.info(\n                    \"url: \" + self.url + \" has a duration of \" + str(self.duration / 60) + \" min -- too long\")\n                raise ValidationFailedError(tr('too_long', song=self.format_title(),\n                                               duration=format_time(self.duration),\n                                               max_duration=format_time(max_duration)))\n            else:\n                self.ready = \"validated\"\n                self.version += 1  # notify wrapper to save me\n                return True\n        finally:\n            self.validating_lock.release()\n\n    # Run in a other thread\n    def prepare(self):\n        if not self.downloading:\n            assert self.ready == 'validated'\n            return self._download()\n        else:\n            assert self.ready == 'yes'\n            return True\n\n    def _get_info_from_url(self):\n        self.log.info(\"url: fetching metadata of url %s \" % self.url)\n        ydl_opts = {\n            'noplaylist': True\n        }\n\n        cookie = var.config.get('youtube_dl', 'cookie_file')\n        if cookie:\n            ydl_opts['cookiefile'] = var.config.get('youtube_dl', 'cookie_file')\n\n        user_agent = var.config.get('youtube_dl', 'user_agent')\n        if user_agent:\n            youtube_dl.utils.std_headers['User-Agent'] = var.config.get('youtube_dl', 'user_agent')\\\n\n        succeed = False\n        with youtube_dl.YoutubeDL(ydl_opts) as ydl:\n            attempts = var.config.getint('bot', 'download_attempts')\n            for i in range(attempts):\n                try:\n                    info = ydl.extract_info(self.url, download=False)\n                    self.duration = info['duration']\n                    self.title = info['title'].strip()\n                    self.keywords = self.title\n                    succeed = True\n                    return True\n                except youtube_dl.utils.DownloadError:\n                    pass\n                except KeyError:  # info has no 'duration'\n                    break\n\n        if not succeed:\n            self.ready = 'failed'\n            self.log.error(\"url: error while fetching info from the URL\")\n            raise ValidationFailedError(tr('unable_download', item=self.format_title()))\n\n    def _download(self):\n        util.clear_tmp_folder(var.tmp_folder, var.config.getint('bot', 'tmp_folder_max_size'))\n\n        self.downloading = True\n        base_path = var.tmp_folder + self.id\n        save_path = base_path\n\n        # Download only if music is not existed\n        self.ready = \"preparing\"\n\n        self.log.info(\"bot: downloading url (%s) %s \" % (self.title, self.url))\n        ydl_opts = {\n            'format': 'bestaudio/best',\n            'outtmpl': base_path,\n            'noplaylist': True,\n            'writethumbnail': True,\n            'updatetime': False,\n            'verbose': var.config.getboolean('debug', 'youtube_dl'),\n            'postprocessors': [{\n                'key': 'FFmpegThumbnailsConvertor',\n                'format': 'jpg',\n                'when': 'before_dl'\n            }]\n        }\n\n        cookie = var.config.get('youtube_dl', 'cookie_file')\n        if cookie:\n            ydl_opts['cookiefile'] = var.config.get('youtube_dl', 'cookie_file')\n\n        user_agent = var.config.get('youtube_dl', 'user_agent')\n        if user_agent:\n            youtube_dl.utils.std_headers['User-Agent'] = var.config.get('youtube_dl', 'user_agent')\n\n        with youtube_dl.YoutubeDL(ydl_opts) as ydl:\n            attempts = var.config.getint('bot', 'download_attempts')\n            download_succeed = False\n            for i in range(attempts):\n                self.log.info(\"bot: download attempts %d / %d\" % (i + 1, attempts))\n                try:\n                    ydl.extract_info(self.url)\n                    download_succeed = True\n                    break\n                except:\n                    error_traceback = traceback.format_exc().split(\"During\")[0]\n                    error = error_traceback.rstrip().split(\"\\n\")[-1]\n                    self.log.error(\"bot: download failed with error:\\n %s\" % error)\n\n            if download_succeed:\n                self.path = save_path\n                self.ready = \"yes\"\n                self.log.info(\n                    \"bot: finished downloading url (%s) %s, saved to %s.\" % (self.title, self.url, self.path))\n                self.downloading = False\n                self._read_thumbnail_from_file(base_path + \".jpg\")\n                self.version += 1  # notify wrapper to save me\n                return True\n            else:\n                for f in glob.glob(base_path + \"*\"):\n                    os.remove(f)\n                self.ready = \"failed\"\n                self.downloading = False\n                raise PreparationFailedError(tr('unable_download', item=self.format_title()))\n\n    def _read_thumbnail_from_file(self, path_thumbnail):\n        if os.path.isfile(path_thumbnail):\n            im = Image.open(path_thumbnail)\n            self.thumbnail = self._prepare_thumbnail(im)\n\n    def _prepare_thumbnail(self, im):\n        im.thumbnail((100, 100), Image.LANCZOS)\n        buffer = BytesIO()\n        im = im.convert('RGB')\n        im.save(buffer, format=\"JPEG\")\n        return base64.b64encode(buffer.getvalue()).decode('utf-8')\n\n    def to_dict(self):\n        dict = super().to_dict()\n        dict['type'] = 'url'\n        dict['url'] = self.url\n        dict['duration'] = self.duration\n        dict['path'] = self.path\n        dict['title'] = self.title\n        dict['thumbnail'] = self.thumbnail\n\n        return dict\n\n    def format_debug_string(self):\n        return \"[url] {title} ({url})\".format(\n            title=self.title,\n            url=self.url\n        )\n\n    def format_song_string(self, user):\n        if self.ready in ['validated', 'yes']:\n            return tr(\"url_item\",\n                      title=self.title if self.title else \"??\",\n                      url=self.url,\n                      user=user)\n        return self.url\n\n    def format_current_playing(self, user):\n        display = tr(\"now_playing\", item=self.format_song_string(user))\n\n        if self.thumbnail:\n            thumbnail_html = '<img width=\"80\" src=\"data:image/jpge;base64,' + \\\n                             self.thumbnail + '\"/>'\n            display += \"<br />\" + thumbnail_html\n\n        return display\n\n    def format_title(self):\n        return self.title if self.title else self.url\n\n    def display_type(self):\n        return tr(\"url\")\n"
  },
  {
    "path": "media/url_from_playlist.py",
    "content": "import logging\nimport yt_dlp as youtube_dl\nfrom constants import tr_cli as tr\nimport variables as var\nfrom media.item import item_builders, item_loaders, item_id_generators\nfrom media.url import URLItem, url_item_id_generator\n\n\nlog = logging.getLogger(\"bot\")\n\n\ndef get_playlist_info(url, start_index=0, user=\"\"):\n    ydl_opts = {\n        'extract_flat': 'in_playlist',\n        'verbose': var.config.getboolean('debug', 'youtube_dl')\n    }\n\n    cookie = var.config.get('youtube_dl', 'cookie_file')\n    if cookie:\n        ydl_opts['cookiefile'] = var.config.get('youtube_dl', 'cookie_file')\n\n    user_agent = var.config.get('youtube_dl', 'user_agent')\n    if user_agent:\n        youtube_dl.utils.std_headers['User-Agent'] = var.config.get('youtube_dl', 'user_agent')\n\n    with youtube_dl.YoutubeDL(ydl_opts) as ydl:\n        attempts = var.config.getint('bot', 'download_attempts')\n        for i in range(attempts):\n            items = []\n            try:\n                info = ydl.extract_info(url, download=False)\n                # # if url is not a playlist but a video\n                # if 'entries' not in info and 'webpage_url' in info:\n                #     music = {'type': 'url',\n                #              'title': info['title'],\n                #              'url': info['webpage_url'],\n                #              'user': user,\n                #              'ready': 'validation'}\n                #     items.append(music)\n                #     return items\n\n                playlist_title = info['title']\n                for j in range(start_index, min(len(info['entries']),\n                                                start_index + var.config.getint('bot', 'max_track_playlist'))):\n                    # Unknow String if No title into the json\n                    title = info['entries'][j]['title'] if 'title' in info['entries'][j] else \"Unknown Title\"\n                    # Add youtube url if the url in the json isn't a full url\n                    item_url = info['entries'][j]['url'] if info['entries'][j]['url'][0:4] == 'http' \\\n                        else \"https://www.youtube.com/watch?v=\" + info['entries'][j]['url']\n                    print(info['entries'][j])\n\n                    music = {\n                        \"type\": \"url_from_playlist\",\n                        \"url\": item_url,\n                        \"title\": title,\n                        \"playlist_url\": url,\n                        \"playlist_title\": playlist_title,\n                        \"user\": user\n                    }\n\n                    items.append(music)\n\n            except Exception as ex:\n                log.exception(ex, exc_info=True)\n                continue\n\n            return items\n\n\ndef playlist_url_item_builder(**kwargs):\n    return PlaylistURLItem(kwargs['url'],\n                           kwargs['title'],\n                           kwargs['playlist_url'],\n                           kwargs['playlist_title'])\n\n\ndef playlist_url_item_loader(_dict):\n    return PlaylistURLItem(\"\", \"\", \"\", \"\", _dict)\n\n\nitem_builders['url_from_playlist'] = playlist_url_item_builder\nitem_loaders['url_from_playlist'] = playlist_url_item_loader\nitem_id_generators['url_from_playlist'] = url_item_id_generator\n\n\nclass PlaylistURLItem(URLItem):\n    def __init__(self, url, title, playlist_url, playlist_title, from_dict=None):\n        if from_dict is None:\n            super().__init__(url)\n            self.title = title\n            self.playlist_url = playlist_url\n            self.playlist_title = playlist_title\n        else:\n            super().__init__(\"\", from_dict)\n            self.playlist_title = from_dict['playlist_title']\n            self.playlist_url = from_dict['playlist_url']\n\n        self.type = \"url_from_playlist\"\n\n    def to_dict(self):\n        tmp_dict = super().to_dict()\n        tmp_dict['playlist_url'] = self.playlist_url\n        tmp_dict['playlist_title'] = self.playlist_title\n\n        return tmp_dict\n\n    def format_debug_string(self):\n        return \"[url] {title} ({url}) from playlist {playlist}\".format(\n            title=self.title,\n            url=self.url,\n            playlist=self.playlist_title\n        )\n\n    def format_song_string(self, user):\n        return tr(\"url_from_playlist_item\",\n                  title=self.title,\n                  url=self.url,\n                  playlist_url=self.playlist_url,\n                  playlist=self.playlist_title,\n                  user=user)\n\n    def format_current_playing(self, user):\n        display = tr(\"now_playing\", item=self.format_song_string(user))\n\n        if self.thumbnail:\n            thumbnail_html = '<img width=\"80\" src=\"data:image/jpge;base64,' + \\\n                             self.thumbnail + '\"/>'\n            display += \"<br />\" + thumbnail_html\n\n        return display\n\n    def display_type(self):\n        return tr(\"url_from_playlist\")\n"
  },
  {
    "path": "mumbleBot.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\nimport re\nimport threading\nimport time\nimport sys\nimport math\nimport signal\nimport configparser\nimport audioop\nimport subprocess as sp\nimport argparse\nimport os\nimport os.path\nimport pymumble_py3 as pymumble\nimport pymumble_py3.constants\nimport variables as var\nimport logging\nimport logging.handlers\nimport traceback\nimport struct\nfrom packaging import version\n\nimport util\nimport command\nimport constants\nimport media.playlist\nfrom constants import tr_cli as tr\nfrom database import SettingsDatabase, MusicDatabase, DatabaseMigration\nfrom media.item import ValidationFailedError, PreparationFailedError\nfrom media.cache import MusicCache\n\n\nclass MumbleBot:\n    version = 'git'\n\n    def __init__(self, args):\n        self.log = logging.getLogger(\"bot\")\n        self.log.info(f\"bot: botamusique version {self.get_version()}, starting...\")\n        signal.signal(signal.SIGINT, self.ctrl_caught)\n        self.cmd_handle = {}\n\n        self.stereo = var.config.getboolean('bot', 'stereo')\n\n        if args.channel:\n            self.channel = args.channel\n        else:\n            self.channel = var.config.get(\"server\", \"channel\")\n\n        var.user = args.user\n        var.is_proxified = var.config.getboolean(\n            \"webinterface\", \"is_web_proxified\")\n\n        # Flags to indicate the bot is exiting (Ctrl-C, or !kill)\n        self.exit = False\n        self.nb_exit = 0\n\n        # Related to ffmpeg thread\n        self.thread = None\n        self.thread_stderr = None\n        self.read_pcm_size = 0\n        self.pcm_buffer_size = 0\n        self.last_ffmpeg_err = \"\"\n\n        # Play/pause status\n        self.is_pause = False\n        self.pause_at_id = \"\"\n        self.playhead = -1  # current position in a song.\n        self.song_start_at = -1\n        self.wait_for_ready = False  # flag for the loop are waiting for download to complete in the other thread\n\n        #\n        self.on_interrupting = False\n\n        if args.host:\n            host = args.host\n        else:\n            host = var.config.get(\"server\", \"host\")\n\n        if args.port:\n            port = args.port\n        else:\n            port = var.config.getint(\"server\", \"port\")\n\n        if args.password:\n            password = args.password\n        else:\n            password = var.config.get(\"server\", \"password\")\n\n        if args.channel:\n            self.channel = args.channel\n        else:\n            self.channel = var.config.get(\"server\", \"channel\")\n\n        if args.certificate:\n            certificate = args.certificate\n        else:\n            certificate = util.solve_filepath(var.config.get(\"server\", \"certificate\"))\n\n        if args.tokens:\n            tokens = args.tokens\n        else:\n            tokens = var.config.get(\"server\", \"tokens\")\n            tokens = tokens.split(',')\n\n\n        if args.user:\n            self.username = args.user\n        else:\n            self.username = var.config.get(\"bot\", \"username\")\n\n        if args.bandwidth:\n            self.bandwidth = args.bandwidth\n        else:\n            self.bandwidth = var.config.getint(\"bot\", \"bandwidth\")\n\n        self.mumble = pymumble.Mumble(host, user=self.username, port=port, password=password, tokens=tokens,\n                                      stereo=self.stereo,\n                                      debug=var.config.getboolean('debug', 'mumble_connection'),\n                                      certfile=certificate)\n        self.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_TEXTMESSAGERECEIVED, self.message_received)\n\n        self.mumble.set_codec_profile(\"audio\")\n        self.mumble.start()  # start the mumble thread\n        self.mumble.is_ready()  # wait for the connection\n\n        if self.mumble.connected >= pymumble.constants.PYMUMBLE_CONN_STATE_FAILED:\n            exit()\n\n        self.set_comment()\n        self.set_avatar()\n        self.mumble.users.myself.unmute()  # by sure the user is not muted\n        self.join_channel()\n        self.mumble.set_bandwidth(self.bandwidth)\n\n        bots = var.config.get(\"bot\", \"when_nobody_in_channel_ignore\",fallback=\"\")\n        self.bots = set(bots.split(','))\n        self._user_in_channel = self.get_user_count_in_channel()\n\n\n        # ====== Volume ======\n        self.volume_helper = util.VolumeHelper()\n\n        max_vol = var.config.getfloat('bot', 'max_volume')\n        if var.db.has_option('bot', 'max_volume'):\n            max_vol = var.db.getfloat('bot', 'max_volume')                \n        _volume = var.config.getfloat('bot', 'volume')\n        if var.db.has_option('bot', 'volume'):\n            _volume = var.db.getfloat('bot', 'volume')\n        _volume = min(_volume, max_vol)\n        self.volume_helper.set_volume(_volume)\n\n        self.is_ducking = False\n        self.on_ducking = False\n        self.ducking_release = time.time()\n        self.last_volume_cycle_time = time.time()\n\n        self._ducking_volume = 0\n        _ducking_volume = var.config.getfloat(\"bot\", \"ducking_volume\")\n        _ducking_volume = var.db.getfloat(\"bot\", \"ducking_volume\", fallback=_ducking_volume)\n        self.volume_helper.set_ducking_volume(_ducking_volume)\n\n        self.ducking_threshold = var.config.getfloat(\"bot\", \"ducking_threshold\")\n        self.ducking_threshold = var.db.getfloat(\"bot\", \"ducking_threshold\", fallback=self.ducking_threshold)\n\n        if not var.db.has_option(\"bot\", \"ducking\") and var.config.getboolean(\"bot\", \"ducking\") \\\n                or var.config.getboolean(\"bot\", \"ducking\"):\n            self.is_ducking = True\n            self.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_SOUNDRECEIVED,\n                                               self.ducking_sound_received)\n            self.mumble.set_receive_sound(True)\n\n        assert var.config.get(\"bot\", \"when_nobody_in_channel\") in ['pause', 'pause_resume', 'stop', 'nothing', ''], \\\n            \"Unknown action for when_nobody_in_channel\"\n\n        if var.config.get(\"bot\", \"when_nobody_in_channel\") in ['pause', 'pause_resume', 'stop']:\n            user_change_callback = \\\n                lambda user, action: threading.Thread(target=self.users_changed,\n                                                      args=(user, action), daemon=True).start()\n            self.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_USERREMOVED, user_change_callback)\n            self.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_USERUPDATED, user_change_callback)\n\n        # Debug use\n        self._loop_status = 'Idle'\n        self._display_rms = False\n        self._max_rms = 0\n\n        self.redirect_ffmpeg_log = var.config.getboolean('debug', 'redirect_ffmpeg_log')\n\n        if var.config.getboolean(\"bot\", \"auto_check_update\"):\n            def check_update():\n                nonlocal self\n                new_version, changelog = util.check_update(self.get_version())\n                if new_version:\n                    self.send_channel_msg(tr('new_version_found', new_version=new_version, changelog=changelog))\n\n            th = threading.Thread(target=check_update, name=\"UpdateThread\")\n            th.daemon = True\n            th.start()\n\n        last_startup_version = var.db.get(\"bot\", \"version\", fallback=None)\n        try:\n            if not last_startup_version or version.parse(last_startup_version) < version.parse(self.version):\n                var.db.set(\"bot\", \"version\", self.version)\n                if var.config.getboolean(\"bot\", \"auto_check_update\"):\n                    changelog = util.fetch_changelog()\n                    self.send_channel_msg(tr(\"update_successful\", version=self.version, changelog=changelog))\n        except version.InvalidVersion:\n            var.db.set(\"bot\", \"version\", self.version)\n\n    # Set the CTRL+C shortcut\n    def ctrl_caught(self, signal, frame):\n        self.log.info(\n            \"\\nSIGINT caught, quitting, {} more to kill\".format(2 - self.nb_exit))\n\n        if var.config.getboolean('bot', 'save_playlist') \\\n                and var.config.get(\"bot\", \"save_music_library\"):\n            self.log.info(\"bot: save playlist into database\")\n            var.playlist.save()\n\n        if self.nb_exit > 1:\n            self.log.info(\"Forced Quit\")\n            sys.exit(0)\n        self.nb_exit += 1\n\n        self.exit = True\n\n    def get_version(self):\n        if self.version != \"git\":\n            return self.version\n        else:\n            return util.get_snapshot_version()\n\n    def register_command(self, cmd, handle, no_partial_match=False, access_outside_channel=False, admin=False):\n        cmds = cmd.split(\",\")\n        for command in cmds:\n            command = command.strip()\n            if command:\n                self.cmd_handle[command] = {'handle': handle,\n                                            'partial_match': not no_partial_match,\n                                            'access_outside_channel': access_outside_channel,\n                                            'admin': admin}\n                self.log.debug(\"bot: command added: \" + command)\n\n    def set_comment(self):\n        self.mumble.users.myself.comment(var.config.get('bot', 'comment'))\n\n    def set_avatar(self):\n        avatar_path = var.config.get('bot', 'avatar')\n\n        if avatar_path:\n            with open(avatar_path, 'rb') as avatar_file:\n                self.mumble.users.myself.texture(avatar_file.read())\n        else:\n            self.mumble.users.myself.texture(b'')\n\n    def join_channel(self):\n        if self.channel:\n            if '/' in self.channel:\n                self.mumble.channels.find_by_tree(self.channel.split('/')).move_in()\n            else:\n                self.mumble.channels.find_by_name(self.channel).move_in()\n\n    # =======================\n    #         Message\n    # =======================\n\n    # All text send to the chat is analysed by this function\n    def message_received(self, text):\n        raw_message = text.message.strip()\n        message = re.sub(r'<.*?>', '', raw_message)\n        if text.actor == 0:\n            # Some server will send a welcome message to the bot once connected.\n            # It doesn't have a valid \"actor\". Simply ignore it here.\n            return\n\n        user = self.mumble.users[text.actor]['name']\n\n        if var.config.getboolean('commands', 'split_username_at_space'):\n            # in can you use https://github.com/Natenom/mumblemoderator-module-collection/tree/master/os-suffixes ,\n            # you want to split the username\n            user = user.split()[0]\n\n        command_symbols = var.config.get('commands', 'command_symbol')\n        match = re.match(fr'^[{re.escape(command_symbols)}](?P<command>\\S+)(?:\\s(?P<argument>.*))?', message)\n        if match:\n            command = match.group(\"command\").lower()\n            argument = match.group(\"argument\") or \"\"\n\n            if not command:\n                return\n\n            self.log.info(f'bot: received command \"{command}\" with arguments \"{argument}\" from {user}')\n\n            # Anti stupid guy function\n            if not self.is_admin(user) and not var.config.getboolean('bot', 'allow_private_message') and text.session:\n                self.mumble.users[text.actor].send_text_message(\n                    tr('pm_not_allowed'))\n                return\n\n            for i in var.db.items(\"user_ban\"):\n                if user.lower() == i[0]:\n                    self.mumble.users[text.actor].send_text_message(\n                        tr('user_ban'))\n                    return\n\n            if not self.is_admin(user) and argument:\n                input_url = util.get_url_from_input(argument)\n                if input_url and var.db.has_option('url_ban', input_url):\n                    self.mumble.users[text.actor].send_text_message(\n                        tr('url_ban'))\n                    return\n\n            command_exc = \"\"\n            try:\n                if command in self.cmd_handle:\n                    command_exc = command\n                else:\n                    # try partial match\n                    cmds = self.cmd_handle.keys()\n                    matches = []\n                    for cmd in cmds:\n                        if cmd.startswith(command) and self.cmd_handle[cmd]['partial_match']:\n                            matches.append(cmd)\n\n                    if len(matches) == 1:\n                        self.log.info(\"bot: {:s} matches {:s}\".format(command, matches[0]))\n                        command_exc = matches[0]\n\n                    elif len(matches) > 1:\n                        self.mumble.users[text.actor].send_text_message(\n                            tr('which_command', commands=\"<br>\".join(matches)))\n                        return\n                    else:\n                        self.mumble.users[text.actor].send_text_message(\n                            tr('bad_command', command=command))\n                        return\n\n                if self.cmd_handle[command_exc]['admin'] and not self.is_admin(user):\n                    self.mumble.users[text.actor].send_text_message(tr('not_admin'))\n                    return\n\n                if not self.cmd_handle[command_exc]['access_outside_channel'] \\\n                        and not self.is_admin(user) \\\n                        and not var.config.getboolean('bot', 'allow_other_channel_message') \\\n                        and self.mumble.users[text.actor]['channel_id'] != self.mumble.users.myself['channel_id']:\n                    self.mumble.users[text.actor].send_text_message(\n                        tr('not_in_my_channel'))\n                    return\n\n                self.cmd_handle[command_exc]['handle'](self, user, text, command_exc, argument)\n            except:\n                error_traceback = traceback.format_exc()\n                error = error_traceback.rstrip().split(\"\\n\")[-1]\n                self.log.error(f\"bot: command {command_exc} failed with error: {error_traceback}\\n\")\n                self.send_msg(tr('error_executing_command', command=command_exc, error=error), text)\n\n    def send_msg(self, msg, text):\n        msg = msg.encode('utf-8', 'ignore').decode('utf-8')\n        # text if the object message, contain information if direct message or channel message\n        self.mumble.users[text.actor].send_text_message(msg)\n\n    def send_channel_msg(self, msg):\n        msg = msg.encode('utf-8', 'ignore').decode('utf-8')\n        own_channel = self.mumble.channels[self.mumble.users.myself['channel_id']]\n        own_channel.send_text_message(msg)\n\n    @staticmethod\n    def is_admin(user):\n        list_admin = var.config.get('bot', 'admin').rstrip().split(';')\n        if user in list_admin:\n            return True\n        else:\n            return False\n\n    # =======================\n    #   Other Mumble Events\n    # =======================\n\n    def get_user_count_in_channel(self):\n        # Get the channel, based on the channel id\n        own_channel = self.mumble.channels[self.mumble.users.myself['channel_id']]\n\n        # Build set of unique usernames\n        users = set([user.get_property(\"name\") for user in own_channel.get_users()])\n\n        # Exclude all bots from the set of usernames\n        users = users.difference(self.bots)\n\n        # Return the number of elements in the set, as the final user count\n        return len(users)\n\n\n    def users_changed(self, user, message):\n        # only check if there is one more user currently in the channel\n        # else when the music is paused and somebody joins, music would start playing again\n        user_count = self.get_user_count_in_channel()\n\n        if user_count > self._user_in_channel and user_count == 2:\n            if var.config.get(\"bot\", \"when_nobody_in_channel\") == \"pause_resume\":\n                self.resume()\n            elif var.config.get(\"bot\", \"when_nobody_in_channel\") == \"pause\" and self.is_pause:\n                self.send_channel_msg(tr(\"auto_paused\"))\n        elif user_count == 1 and len(var.playlist) != 0:\n            # if the bot is the only user left in the channel and the playlist isn't empty\n            if var.config.get(\"bot\", \"when_nobody_in_channel\") == \"stop\":\n                self.log.info('bot: No user in my channel. Stop music now.')\n                self.clear()\n            else:\n                self.log.info('bot: No user in my channel. Pause music now.')\n                self.pause()\n\n        self._user_in_channel = user_count\n\n    # =======================\n    #   Launch and Download\n    # =======================\n\n    def launch_music(self, music_wrapper, start_from=0):\n        assert music_wrapper.is_ready()\n\n        uri = music_wrapper.uri()\n\n        self.log.info(\"bot: play music \" + music_wrapper.format_debug_string())\n\n        if var.config.getboolean('bot', 'announce_current_music'):\n            self.send_channel_msg(music_wrapper.format_current_playing())\n\n        if var.config.getboolean('debug', 'ffmpeg'):\n            ffmpeg_debug = \"debug\"\n        else:\n            ffmpeg_debug = \"warning\"\n\n        channels = 2 if self.stereo else 1\n        self.pcm_buffer_size = 960 * channels\n\n        command = (\"ffmpeg\", '-v', ffmpeg_debug, '-nostdin', '-i',\n                   uri, '-ss', f\"{start_from:f}\", '-ac', str(channels), '-f', 's16le', '-ar', '48000', '-')\n        self.log.debug(\"bot: execute ffmpeg command: \" + \" \".join(command))\n\n        # The ffmpeg process is a thread\n        # prepare pipe for catching stderr of ffmpeg\n        if self.redirect_ffmpeg_log:\n            pipe_rd, pipe_wd = util.pipe_no_wait()  # Let the pipe work in non-blocking mode\n            self.thread_stderr = os.fdopen(pipe_rd)\n        else:\n            pipe_rd, pipe_wd = None, None\n\n        self.thread = sp.Popen(command, stdout=sp.PIPE, stderr=pipe_wd, bufsize=self.pcm_buffer_size)\n\n    def async_download_next(self):\n        # Function start if the next music isn't ready\n        # Do nothing in case the next music is already downloaded\n        self.log.debug(\"bot: Async download next asked \")\n        while var.playlist.next_item():\n            # usually, all validation will be done when adding to the list.\n            # however, for performance consideration, youtube playlist won't be validate when added.\n            # the validation has to be done here.\n            next = var.playlist.next_item()\n            try:\n                if not next.is_ready():\n                    self.async_download(next)\n\n                break\n            except ValidationFailedError as e:\n                self.send_channel_msg(e.msg)\n                var.playlist.remove_by_id(next.id)\n                var.cache.free_and_delete(next.id)\n\n    def async_download(self, item):\n        th = threading.Thread(\n            target=self._download, name=\"Prepare-\" + item.id[:7], args=(item,))\n        self.log.info(f\"bot: start preparing item in thread: {item.format_debug_string()}\")\n        th.daemon = True\n        th.start()\n        return th\n\n    def start_download(self, item):\n        if not item.is_ready():\n            self.log.info(\"bot: current music isn't ready, start downloading.\")\n            self.async_download(item)\n            self.send_channel_msg(\n                tr('download_in_progress', item=item.format_title()))\n\n    def _download(self, item):\n        ver = item.version\n        try:\n            item.validate()\n            if item.is_ready():\n                return True\n        except ValidationFailedError as e:\n            self.send_channel_msg(e.msg)\n            var.playlist.remove_by_id(item.id)\n            var.cache.free_and_delete(item.id)\n            return False\n\n        try:\n            item.prepare()\n            if item.version > ver:\n                var.playlist.version += 1\n            return True\n        except PreparationFailedError as e:\n            self.send_channel_msg(e.msg)\n            return False\n\n    # =======================\n    #          Loop\n    # =======================\n\n    # Main loop of the Bot\n    def loop(self):\n        while not self.exit and self.mumble.is_alive():\n\n            while self.thread and self.mumble.sound_output.get_buffer_size() > 0.5 and not self.exit:\n                # If the buffer isn't empty, I cannot send new music part, so I wait\n                self._loop_status = f'Wait for buffer {self.mumble.sound_output.get_buffer_size():.3f}'\n                time.sleep(0.01)\n\n            raw_music = None\n            if self.thread:\n                # I get raw from ffmpeg thread\n                # move playhead forward\n                self._loop_status = 'Reading raw'\n                if self.song_start_at == -1:\n                    self.song_start_at = time.time() - self.playhead\n                self.playhead = time.time() - self.song_start_at\n\n                raw_music = self.thread.stdout.read(self.pcm_buffer_size)\n                self.read_pcm_size += len(raw_music)\n\n                if self.redirect_ffmpeg_log:\n                    try:\n                        self.last_ffmpeg_err = self.thread_stderr.readline()\n                        if self.last_ffmpeg_err:\n                            self.log.debug(\"ffmpeg: \" + self.last_ffmpeg_err.strip(\"\\n\"))\n                    except:\n                        pass\n\n                if raw_music:\n                    # Adjust the volume and send it to mumble\n                    self.volume_cycle()\n\n                    if not self.on_interrupting and len(raw_music) == self.pcm_buffer_size:\n                        self.mumble.sound_output.add_sound(\n                            audioop.mul(raw_music, 2, self.volume_helper.real_volume))\n                    elif self.read_pcm_size == 0:\n                        self.mumble.sound_output.add_sound(\n                            audioop.mul(self._fadeout(raw_music, self.stereo, fadein=True), 2, self.volume_helper.real_volume))\n                    elif self.on_interrupting or len(raw_music) < self.pcm_buffer_size:\n                        self.mumble.sound_output.add_sound(\n                            audioop.mul(self._fadeout(raw_music, self.stereo, fadein=False), 2, self.volume_helper.real_volume))\n                        self.thread.kill()\n                        self.thread = None\n                        time.sleep(0.1)\n                        self.on_interrupting = False\n                else:\n                    time.sleep(0.1)\n            else:\n                time.sleep(0.1)\n\n            if not self.is_pause and not raw_music:\n                self.thread = None\n                # bot is not paused, but ffmpeg thread has gone.\n                # indicate that last song has finished, or the bot just resumed from pause, or something is wrong.\n                if self.read_pcm_size < self.pcm_buffer_size \\\n                        and var.playlist.current_index != -1 \\\n                        and self.last_ffmpeg_err:\n                    current = var.playlist.current_item()\n                    self.log.error(\"bot: cannot play music %s\", current.format_debug_string())\n                    self.log.error(\"bot: with ffmpeg error: %s\", self.last_ffmpeg_err)\n                    self.last_ffmpeg_err = \"\"\n\n                    self.send_channel_msg(tr('unable_play', item=current.format_title()))\n                    var.playlist.remove_by_id(current.id)\n                    var.cache.free_and_delete(current.id)\n\n                # move to the next song.\n                if not self.wait_for_ready:  # if wait_for_ready flag is not true, move to the next song.\n                    if var.playlist.next():\n                        current = var.playlist.current_item()\n                        self.log.debug(f\"bot: next into the song: {current.format_debug_string()}\")\n                        try:\n                            self.start_download(current)\n                            self.wait_for_ready = True\n\n                            self.song_start_at = -1\n                            self.playhead = 0\n\n                        except ValidationFailedError as e:\n                            self.send_channel_msg(e.msg)\n                            var.playlist.remove_by_id(current.id)\n                            var.cache.free_and_delete(current.id)\n                    else:\n                        self._loop_status = 'Empty queue'\n                else:\n                    # if wait_for_ready flag is true, means the pointer is already\n                    # pointing to target song. start playing\n                    current = var.playlist.current_item()\n                    if current:\n                        if current.is_ready():\n                            self.wait_for_ready = False\n                            self.read_pcm_size = 0\n\n                            self.launch_music(current, self.playhead)\n                            self.last_volume_cycle_time = time.time()\n                            self.async_download_next()\n                        elif current.is_failed():\n                            var.playlist.remove_by_id(current.id)\n                            self.wait_for_ready = False\n                        else:\n                            self._loop_status = 'Wait for the next item to be ready'\n                    else:\n                        self.wait_for_ready = False\n\n        while self.mumble.sound_output.get_buffer_size() > 0 and self.mumble.is_alive():\n            # Empty the buffer before exit\n            time.sleep(0.01)\n        time.sleep(0.5)\n\n        if self.exit:\n            self._loop_status = \"exited\"\n            if var.config.getboolean('bot', 'save_playlist') \\\n                    and var.config.get(\"bot\", \"save_music_library\"):\n                self.log.info(\"bot: save playlist into database\")\n                var.playlist.save()\n\n    def volume_cycle(self):\n        delta = time.time() - self.last_volume_cycle_time\n\n        if self.on_ducking and self.ducking_release < time.time():\n            self.on_ducking = False\n            self._max_rms = 0\n\n        if delta > 0.001:\n            if self.is_ducking and self.on_ducking:\n                self.volume_helper.real_volume = \\\n                    (self.volume_helper.real_volume - self.volume_helper.ducking_volume_set) * math.exp(- delta / 0.2) \\\n                    + self.volume_helper.ducking_volume_set\n            else:\n                self.volume_helper.real_volume = self.volume_helper.volume_set - \\\n                                                 (self.volume_helper.volume_set - self.volume_helper.real_volume) * math.exp(- delta / 0.5)\n\n            self.last_volume_cycle_time = time.time()\n\n    def ducking_sound_received(self, user, sound):\n        rms = audioop.rms(sound.pcm, 2)\n        self._max_rms = max(rms, self._max_rms)\n        if self._display_rms:\n            if rms < self.ducking_threshold:\n                print('%6d/%6d  ' % (rms, self._max_rms) + '-' * int(rms / 200), end='\\r')\n            else:\n                print('%6d/%6d  ' % (rms, self._max_rms) + '-' * int(self.ducking_threshold / 200)\n                      + '+' * int((rms - self.ducking_threshold) / 200), end='\\r')\n\n        if rms > self.ducking_threshold:\n            if self.on_ducking is False:\n                self.log.debug(\"bot: ducking triggered\")\n                self.on_ducking = True\n            self.ducking_release = time.time() + 1  # ducking release after 1s\n\n    def _fadeout(self, _pcm_data, stereo=False, fadein=False):\n        pcm_data = bytearray(_pcm_data)\n        if stereo:\n            if not fadein:\n                mask = [math.exp(-x / 60) for x in range(0, int(len(pcm_data) / 4))]\n            else:\n                mask = [math.exp(-x / 60) for x in reversed(range(0, int(len(pcm_data) / 4)))]\n\n            for i in range(int(len(pcm_data) / 4)):\n                pcm_data[4 * i:4 * i + 2] = struct.pack(\"<h\",\n                                                        round(struct.unpack(\"<h\", pcm_data[4 * i:4 * i + 2])[0] * mask[i]))\n                pcm_data[4 * i + 2:4 * i + 4] = struct.pack(\"<h\", round(\n                    struct.unpack(\"<h\", pcm_data[4 * i + 2:4 * i + 4])[0] * mask[i]))\n        else:\n            if not fadein:\n                mask = [math.exp(-x / 60) for x in range(0, int(len(pcm_data) / 2))]\n            else:\n                mask = [math.exp(-x / 60) for x in reversed(range(0, int(len(pcm_data) / 2)))]\n\n            for i in range(int(len(pcm_data) / 2)):\n                pcm_data[2 * i:2 * i + 2] = struct.pack(\"<h\",\n                                                        round(struct.unpack(\"<h\", pcm_data[2 * i:2 * i + 2])[0] * mask[i]))\n\n        return bytes(pcm_data) + bytes(len(pcm_data))\n\n    # =======================\n    #      Play Control\n    # =======================\n\n    def play(self, index=-1, start_at=0):\n        if not self.is_pause:\n            self.interrupt()\n\n        if index != -1:\n            var.playlist.point_to(index)\n\n        current = var.playlist.current_item()\n\n        self.start_download(current)\n        self.is_pause = False\n        self.wait_for_ready = True\n        self.song_start_at = -1\n        self.playhead = start_at\n\n    def clear(self):\n        # Kill the ffmpeg thread and empty the playlist\n        self.interrupt()\n        var.playlist.clear()\n        self.wait_for_ready = False\n        self.log.info(\"bot: music stopped. playlist trashed.\")\n\n    def stop(self):\n        self.interrupt()\n        self.is_pause = True\n        if len(var.playlist) > 0:\n            self.wait_for_ready = True\n        else:\n            self.wait_for_ready = False\n        self.log.info(\"bot: music stopped.\")\n\n    def interrupt(self):\n        # Kill the ffmpeg thread\n        if self.thread:\n            self.on_interrupting = True\n\n            time.sleep(0.1)\n            self.song_start_at = -1\n            self.read_pcm_size = 0\n\n    def pause(self):\n        # Kill the ffmpeg thread\n        self.interrupt()\n        self.is_pause = True\n        self.song_start_at = -1\n        if len(var.playlist) > 0:\n            self.pause_at_id = var.playlist.current_item().id\n            self.log.info(f\"bot: music paused at {self.playhead:.2f} seconds.\")\n\n    def resume(self):\n        self.is_pause = False\n        if var.playlist.current_index == -1:\n            var.playlist.next()\n            self.playhead = 0\n            return\n\n        music_wrapper = var.playlist.current_item()\n\n        if not music_wrapper or not music_wrapper.id == self.pause_at_id or not music_wrapper.is_ready():\n            self.playhead = 0\n            return\n\n        self.wait_for_ready = True\n        self.pause_at_id = \"\"\n\n\ndef start_web_interface(addr, port):\n    global formatter\n    import interface\n\n    # setup logger\n    werkzeug_logger = logging.getLogger('werkzeug')\n    logfile = util.solve_filepath(var.config.get('webinterface', 'web_logfile'))\n    if logfile:\n        handler = logging.handlers.RotatingFileHandler(logfile, mode='a', maxBytes=10240, backupCount=3)  # Rotate after 10KB, leave 3 old logs\n    else:\n        handler = logging.StreamHandler()\n\n    werkzeug_logger.addHandler(handler)\n\n    interface.init_proxy()\n    interface.web.env = 'development'\n    interface.web.secret_key = var.config.get('webinterface', 'flask_secret')\n    interface.web.run(port=port, host=addr)\n\n\nif __name__ == '__main__':\n    supported_languages = util.get_supported_language()\n\n    parser = argparse.ArgumentParser(\n        description='Bot for playing music on Mumble')\n\n    # General arguments\n    parser.add_argument(\"--config\", dest='config', type=str, default='configuration.ini',\n                        help='Load configuration from this file. Default: configuration.ini')\n    parser.add_argument(\"--db\", dest='db', type=str,\n                        default=None, help='Settings database file')\n    parser.add_argument(\"--music-db\", dest='music_db', type=str,\n                        default=None, help='Music library database file')\n    parser.add_argument(\"--lang\", dest='lang', type=str, default=None,\n                        help='Preferred language. Support ' + \", \".join(supported_languages))\n\n    parser.add_argument(\"-q\", \"--quiet\", dest=\"quiet\",\n                        action=\"store_true\", help=\"Only Error logs\")\n    parser.add_argument(\"-v\", \"--verbose\", dest=\"verbose\",\n                        action=\"store_true\", help=\"Show debug log\")\n\n    # Mumble arguments\n    parser.add_argument(\"-s\", \"--server\", dest=\"host\",\n                        type=str, help=\"Hostname of the Mumble server\")\n    parser.add_argument(\"-u\", \"--user\", dest=\"user\",\n                        type=str, help=\"Username for the bot\")\n    parser.add_argument(\"-P\", \"--password\", dest=\"password\",\n                        type=str, help=\"Server password, if required\")\n    parser.add_argument(\"-T\", \"--tokens\", dest=\"tokens\",\n                        type=str, help=\"Server tokens to enter a channel, if required (multiple entries separated with comma ','\")\n    parser.add_argument(\"-p\", \"--port\", dest=\"port\",\n                        type=int, help=\"Port for the Mumble server\")\n    parser.add_argument(\"-c\", \"--channel\", dest=\"channel\",\n                        type=str, help=\"Default channel for the bot\")\n    parser.add_argument(\"-C\", \"--cert\", dest=\"certificate\",\n                        type=str, default=None, help=\"Certificate file\")\n    parser.add_argument(\"-b\", \"--bandwidth\", dest=\"bandwidth\",\n                        type=int, help=\"Bandwidth used by the bot\")\n\n    args = parser.parse_args()\n\n    # ======================\n    #     Load Config\n    # ======================\n\n    config = configparser.ConfigParser(interpolation=None, allow_no_value=True)\n    default_config = configparser.ConfigParser(interpolation=None, allow_no_value=True)\n    var.config = config\n\n    if len(default_config.read(\n            util.solve_filepath('configuration.default.ini'),\n            encoding='utf-8')) == 0:\n        logging.error(\"Could not read default configuration file 'configuration.default.ini', please check\"\n                      \"your installation.\")\n        sys.exit()\n\n    if len(config.read(\n            [util.solve_filepath('configuration.default.ini'), util.solve_filepath(args.config)],\n            encoding='utf-8')) == 0:\n        logging.error(f'Could not read configuration from file \"{args.config}\"')\n        sys.exit()\n\n    extra_configs = util.check_extra_config(config, default_config)\n    if extra_configs:\n        extra_str = \", \".join([f\"'[{k}] {v}'\" for (k, v) in extra_configs])\n        logging.error(f'Unexpected config items {extra_str} defined in your config file. '\n                      f'This is likely caused by a recent change in the names of config items, '\n                      f'or the removal of obsolete config items. Please refer to the changelog.')\n        sys.exit()\n\n    # ======================\n    #     Setup Logger\n    # ======================\n\n    bot_logger = logging.getLogger(\"bot\")\n    bot_logger.setLevel(logging.INFO)\n\n    if args.verbose:\n        bot_logger.setLevel(logging.DEBUG)\n        bot_logger.debug(\"Starting in DEBUG loglevel\")\n    elif args.quiet:\n        bot_logger.setLevel(logging.ERROR)\n        bot_logger.error(\"Starting in ERROR loglevel\")\n\n    logfile = util.solve_filepath(var.config.get('bot', 'logfile').strip())\n    handler = None\n    if logfile:\n        print(f\"Redirecting stdout and stderr to log file: {logfile}\")\n        handler = logging.handlers.RotatingFileHandler(logfile, mode='a', maxBytes=10240, backupCount=3)  # Rotate after 10KB, leave 3 old logs\n        if var.config.getboolean(\"bot\", \"redirect_stderr\"):\n            sys.stderr = util.LoggerIOWrapper(bot_logger, logging.INFO,\n                                              fallback_io_buffer=sys.stderr.buffer)\n    else:\n        handler = logging.StreamHandler()\n\n    util.set_logging_formatter(handler, bot_logger.level)\n    bot_logger.addHandler(handler)\n    logging.getLogger(\"root\").addHandler(handler)\n    var.bot_logger = bot_logger\n\n    # ======================\n    #     Load Database\n    # ======================\n    if args.user:\n        username = args.user\n    else:\n        username = var.config.get(\"bot\", \"username\")\n\n    sanitized_username = \"\".join([x if x.isalnum() else \"_\" for x in username])\n    var.settings_db_path = args.db if args.db is not None else util.solve_filepath(\n        config.get(\"bot\", \"database_path\") or f\"settings-{sanitized_username}.db\")\n    var.music_db_path = args.music_db if args.music_db is not None else util.solve_filepath(\n        config.get(\"bot\", \"music_database_path\"))\n\n    var.db = SettingsDatabase(var.settings_db_path)\n\n    if var.config.get(\"bot\", \"save_music_library\"):\n        var.music_db = MusicDatabase(var.music_db_path)\n    else:\n        var.music_db = MusicDatabase(\":memory:\")\n\n    DatabaseMigration(var.db, var.music_db).migrate()\n\n    var.music_folder = util.solve_filepath(var.config.get('bot', 'music_folder'))\n    if not var.music_folder.endswith(os.sep):\n        # The file searching logic assumes that the music folder ends in a /\n        var.music_folder = var.music_folder + os.sep\n    var.tmp_folder = util.solve_filepath(var.config.get('bot', 'tmp_folder'))\n\n    # ======================\n    #      Translation\n    # ======================\n\n    lang = \"\"\n    if args.lang:\n        lang = args.lang\n    else:\n        lang = var.config.get('bot', 'language')\n\n    if lang not in supported_languages:\n        raise KeyError(f\"Unsupported language {lang}\")\n    var.language = lang\n    constants.load_lang(lang)\n\n    # ======================\n    #     Prepare Cache\n    # ======================\n    var.cache = MusicCache(var.music_db)\n\n    if var.config.getboolean(\"bot\", \"refresh_cache_on_startup\"):\n        var.cache.build_dir_cache()\n\n    # ======================\n    #   Load playback mode\n    # ======================\n    playback_mode = None\n    if var.db.has_option(\"playlist\", \"playback_mode\"):\n        playback_mode = var.db.get('playlist', 'playback_mode')\n    else:\n        playback_mode = var.config.get('bot', 'playback_mode')\n\n    if playback_mode in [\"one-shot\", \"repeat\", \"random\", \"autoplay\"]:\n        var.playlist = media.playlist.get_playlist(playback_mode)\n    else:\n        raise KeyError(f\"Unknown playback mode '{playback_mode}'\")\n\n    # ======================\n    #  Create bot instance\n    # ======================\n    var.bot = MumbleBot(args)\n    command.register_all_commands(var.bot)\n\n    # load playlist\n    if var.config.getboolean('bot', 'save_playlist'):\n        var.bot_logger.info(\"bot: load playlist from previous session\")\n        var.playlist.load()\n\n    # ============================\n    #   Start the web interface\n    # ============================\n    if var.config.getboolean(\"webinterface\", \"enabled\"):\n        wi_addr = var.config.get(\"webinterface\", \"listening_addr\")\n        wi_port = var.config.getint(\"webinterface\", \"listening_port\")\n        tt = threading.Thread(\n            target=start_web_interface, name=\"WebThread\", args=(wi_addr, wi_port))\n        tt.daemon = True\n        bot_logger.info('Starting web interface on {}:{}'.format(wi_addr, wi_port))\n        tt.start()\n\n    # Start the main loop.\n    var.bot.loop()\n"
  },
  {
    "path": "requirements.txt",
    "content": "flask\nyt-dlp\npython-magic\nPillow\nmutagen\nrequests\npackaging\npymumble>=1.2\npyradios"
  },
  {
    "path": "scripts/commit_new_translation.sh",
    "content": "#!/usr/bin/env bash\n\ngit remote set-url origin https://azlux:$GITHUB_API@github.com/azlux/botamusique/\n\necho \"=> Fetching for bot-traduora branch...\"\nif git fetch origin bot-traduora; then\n  echo \"==> bot-traduora branch exists\"\n  git branch bot-traduora FETCH_HEAD\n  CREATE_PR=false\nelse\n  echo \"==> bot-traduora branch doesn't exist, create one\"\n  git branch bot-traduora\n  CREATE_PR=true\nfi\ngit checkout bot-traduora\n\necho \"=> Fetching updates from the server...\"\n\n$SOURCE_DIR/scripts/sync_translation.py --lang-dir $SOURCE_DIR/lang/ --client $TRADUORA_R_CLIENT --secret $TRADUORA_R_SECRET --fetch\ngit add lang/*\ngit status\n\nif [ \"$PUSH\" = \"true\" ]; then\n   echo \"=> Pushing updates to bot-traduora branch...\"\n  if GIT_COMMITTER_NAME='Traduora Bot' GIT_COMMITTER_EMAIL='noreply@azlux.fr' git commit -m 'Bot: Update translation' --author \"Traduora Bot <noreply@azlux.fr>\"; then\n    git push origin bot-traduora\n    sleep 2\n    if $CREATE_PR; then GITHUB_USER=\"azlux\" GITHUB_TOKEN=\"$GITHUB_API\"  hub pull-request -m \"Bot: TRADUORA Update\"; fi\n    exit 0\n  fi\n   echo \"==> There's nothing to push.\"\n   exit 0\nfi\n"
  },
  {
    "path": "scripts/sync_translation.py",
    "content": "#!/usr/bin/env python3\n\nimport os\nimport re\nimport argparse\nimport requests\n\nbase_url = \"https://translate.azlux.fr/api/v1\"\nproject_id = \"4aafb197-3282-47b3-a197-0ca870cf6ab2\"\n\nlang_dir = \"\"\n\n\ndef get_access_header(client, secret):\n    data = {\"grant_type\": \"client_credentials\",\n            \"client_id\": client,\n            \"client_secret\": secret}\n\n    r = requests.post(f\"{base_url}/auth/token\", json=data)\n\n    if r.status_code != 200:\n        print(\"Access denied! Please check your client ID or secret.\")\n        exit(1)\n\n    token = r.json()[\"access_token\"]\n\n    headers = {\"Authorization\": \"Bearer \" + token,\n               \"Accept\": \"application/json, text/plain, */*\"}\n\n    return headers\n\n\ndef fetch_translation(r_client, r_secret):\n    headers = get_access_header(r_client, r_secret)\n\n    r = requests.get(f\"{base_url}/projects/{project_id}/translations\", headers=headers)\n    translations = r.json()['data']\n    for translation in translations:\n        lang_code = translation['locale']['code']\n        print(f\" - Fetching {lang_code}\")\n        params = {'locale': lang_code,\n                  'format': 'jsonnested'}\n        r = requests.get(f\"{base_url}/projects/{project_id}/exports\", params=params, headers=headers)\n        with open(os.path.join(lang_dir, f\"{lang_code}.json\"), \"wb\") as f:\n            f.write(r.content)\n\n\ndef push_strings(w_client, w_secret):\n    print(\"Pushing local translation files into the remote host...\")\n    headers = get_access_header(w_client, w_secret)\n\n    lang_files = os.listdir(lang_dir)\n    lang_list = []\n    for lang_file in lang_files:\n        match = re.search(\"([a-z]{2}_[A-Z]{2})\\.json\", lang_file)\n        if match:\n            lang_list.append(match[1])\n\n    for lang in lang_list:\n        print(f\" - Pushing {lang}\")\n\n        params = {'locale': lang,\n                  'format': 'jsonnested'}\n        files = {'file': open(os.path.join(lang_dir, f\"{lang}.json\"), 'r')}\n\n        r = requests.post(f\"{base_url}/projects/{project_id}/imports\", params=params, headers=headers, files=files)\n        assert r.status_code == 200, f\"Unable to push {lang} into remote host. {r.status_code}\"\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(\n        description=\"Sync translation files with azlux's traduora server.\")\n\n    parser.add_argument(\"--lang-dir\", dest=\"lang_dir\",\n                        type=str, help=\"Directory of the lang files.\")\n    parser.add_argument(\"--client\", dest=\"client\",\n                        type=str, help=\"Client ID used to access the server.\")\n    parser.add_argument(\"--secret\", dest=\"secret\",\n                        type=str, help=\"Secret used to access the server.\")\n\n    parser.add_argument(\"--fetch\", dest='fetch', action=\"store_true\",\n                        help='Fetch translation files from the server.')\n    parser.add_argument(\"--push\", dest='push', action=\"store_true\",\n                        help='Push local translation files into the server.')\n\n    args = parser.parse_args()\n\n    lang_dir = args.lang_dir\n\n    if not args.client or not args.secret:\n        print(\"Client ID and secret need to be provided!\")\n        exit(1)\n\n    if args.push:\n        push_strings(args.client, args.secret)\n\n    if args.fetch:\n        fetch_translation(args.client, args.secret)\n\n    print(\"Done.\")\n\n"
  },
  {
    "path": "scripts/translate_templates.py",
    "content": "#!/usr/bin/env python3\nimport argparse\nimport os\nimport json\nimport re\nimport jinja2\n\ndefault_lang_dict = {}\nlang_dict = {}\n\nlang_dir = \"\"\ntemplate_dir = \"\"\n\n\ndef load_lang(lang):\n    with open(os.path.join(lang_dir, f\"{lang}.json\"), \"r\") as f:\n        return json.load(f)\n\n\ndef tr(option):\n    try:\n        if option in lang_dict['web'] and lang_dict['web'][option]:\n            string = lang_dict['web'][option]\n        else:\n            string = default_lang_dict['web'][option]\n        return string\n    except KeyError:\n        raise KeyError(\"Missed strings in language file: '{string}'. \"\n                       .format(string=option))\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(\n        description=\"Populate html templates with translation strings.\")\n\n    parser.add_argument(\"--lang-dir\", dest=\"lang_dir\",\n                        type=str, help=\"Directory of the lang files.\")\n    parser.add_argument(\"--template-dir\", dest=\"template_dir\",\n                        type=str, help=\"Directory of the template files.\")\n\n    args = parser.parse_args()\n    lang_dir = args.lang_dir\n    template_dir = args.template_dir\n\n    html_files = os.listdir(template_dir)\n    for html_file in html_files:\n        match = re.search(\"(.+)\\.template\\.html\", html_file)\n        if match is None:\n            continue\n\n        print(f\"Populating {html_file} with translations...\")\n        basename = match[1]\n        with open(os.path.join(template_dir, f\"{html_file}\"), \"r\") as f:\n            html = f.read()\n\n        lang_files = os.listdir(lang_dir)\n        lang_list = []\n\n        default_lang_dict = load_lang(\"en_US\")\n\n        for lang_file in lang_files:\n            match = re.search(\"([a-z]{2}_[A-Z]{2})\\.json\", lang_file)\n            if match:\n                lang_list.append(match[1])\n\n        template = jinja2.Template(html)\n\n        for lang in lang_list:\n            print(f\" - Populating {lang}...\")\n            lang_dict = load_lang(lang)\n\n            with open(os.path.join(template_dir, f\"{basename}.{lang}.html\"),\n                      \"w\") as f:\n                f.write(template.render(tr=tr))\n    print(\"Done.\")\n"
  },
  {
    "path": "scripts/update_translation_to_server.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\ngit remote set-url origin https://azlux:$GITHUB_API@github.com/azlux/botamusique/\ngit pull origin master\n\necho \"=> Checking if translations in this commit differ from the server...\"\n\ngit branch testing-translation master\ngit checkout testing-translation\n$SOURCE_DIR/scripts/sync_translation.py --lang-dir $SOURCE_DIR/lang/ --client $TRADUORA_R_CLIENT --secret $TRADUORA_R_SECRET --fetch\n\nif [ -z \"$(git diff)\" ]; then\n  echo \"==> No difference found.\"\n  exit 0\nfi\n\necho \"==> Modifications found.\"\necho \"=> Check if the modifications are based on the translations on the server...\"\n\nn=1\nCOMMON_FOUND=false\n\nwhile [ $n -le 20 ]; do\n  echo \"==> Comparing server's translations with master~$n ($(git show --oneline --quiet master~$n))\"\n  CHANGED_LANG_FILE=$(git diff --name-only master~$n | grep \"lang/\" || true)\n  if [ -z \"$CHANGED_LANG_FILE\" ]; then\n    COMMON_FOUND=true\n    break\n  else\n    echo \"==> Modified lang files: $CHANGED_LANG_FILE\"\n  fi\n  (( n++ ))\ndone\n\nif (! $COMMON_FOUND); then\n  echo \"==> CONFLICTS: Previous commits doesn't share the same translations with the server.\"\n  echo \"    There are unmerged translation updates on the server.\"\n  echo \"    Please manually update these changes or wait for the pull request\"\n  echo \"    created by the translation bot get merged.\"\n  exit 1\nfi\n\necho \"==> master~$n ($(git show --oneline --quiet master~$n)) shares the same translations with the server.\"\n\necho \"=> Preparing to push local translation updates to the server...\"\ngit checkout -f master\n$SOURCE_DIR/scripts/sync_translation.py --lang-dir $SOURCE_DIR/lang/ --client $TRADUORA_W_CLIENT --secret $TRADUORA_W_SECRET --push\n\necho \"=> Fix translation format...\"\n$SOURCE_DIR/scripts/sync_translation.py --lang-dir $SOURCE_DIR/lang/ --client $TRADUORA_R_CLIENT --secret $TRADUORA_R_SECRET --fetch\ngit add lang/*\ngit status\nif GIT_COMMITTER_NAME='Traduora Bot' GIT_COMMITTER_EMAIL='noreply@azlux.fr' git commit -m 'Bot: Reformat translation'; then\n  git push origin master\nfi\n\nexit 0\n"
  },
  {
    "path": "static/.gitignore",
    "content": "css/\njs/"
  },
  {
    "path": "update.sh",
    "content": "#!/usr/bin/env bash\n\ncase \"$1\" in\n    stable)\n        curl -Lo /tmp/botamusique.tar.gz https://packages.azlux.fr/botamusique/sources-stable.tar.gz\n        tar -xzf /tmp/botamusique.tar.gz -C /tmp/\n        cp -r /tmp/botamusique/* .\n        rm -r /tmp/botamusique\n        rm -r /tmp/botamusique.tar.gz\n        ;;\n    testing)\n        curl -Lo /tmp/botamusique.tar.gz https://packages.azlux.fr/botamusique/sources-testing.tar.gz\n        tar -xzf /tmp/botamusique.tar.gz -C /tmp/\n        cp -r /tmp/botamusique/* .\n        rm -r /tmp/botamusique\n        rm -r /tmp/botamusique.tar.gz\n        ;;\n    *)\n        ;;\nesac\nexit 0\n"
  },
  {
    "path": "util.py",
    "content": "#!/usr/bin/python3\n# coding=utf-8\n\nimport hashlib\nimport html\nimport magic\nimport os\nimport io\nimport sys\nimport variables as var\nimport zipfile\nimport re\nimport subprocess as sp\nimport logging\nfrom importlib import reload\nfrom sys import platform\nimport traceback\nimport requests\nfrom packaging import version\n\nimport yt_dlp as youtube_dl\nYT_PKG_NAME = 'yt-dlp'\n\nlog = logging.getLogger(\"bot\")\n\n\ndef solve_filepath(path):\n    if not path:\n        return ''\n\n    if path[0] == '/':\n        return path\n    elif os.path.exists(path):\n        return path\n    else:\n        mydir = os.path.dirname(os.path.realpath(__file__))\n        return mydir + '/' + path\n\n\ndef get_recursive_file_list_sorted(path):\n    filelist = []\n    for root, dirs, files in os.walk(path, topdown=True, onerror=None, followlinks=True):\n        relroot = root.replace(path, '', 1)\n        if relroot != '' and relroot in var.config.get('bot', 'ignored_folders'):\n            continue\n        for file in files:\n            if file in var.config.get('bot', 'ignored_files'):\n                continue\n\n            fullpath = os.path.join(path, relroot, file)\n            if not os.access(fullpath, os.R_OK):\n                continue\n\n            try:\n                mime = magic.from_file(fullpath, mime=True)\n                if 'audio' in mime or 'audio' in magic.from_file(fullpath).lower() or 'video' in mime:\n                    filelist.append(os.path.join(relroot, file))\n            except:\n                pass\n\n    filelist.sort()\n    return filelist\n\n\n# - zips files\n# - returns the absolute path of the created zip file\n# - zip file will be in the applications tmp folder (according to configuration)\n# - format of the filename itself = prefix_hash.zip\n#       - prefix can be controlled by the caller\n#       - hash is a sha1 of the string representation of the directories' contents (which are\n#           zipped)\ndef zipdir(files, zipname_prefix=None):\n    zipname = var.tmp_folder\n    if zipname_prefix and '../' not in zipname_prefix:\n        zipname += zipname_prefix.strip().replace('/', '_') + '_'\n\n    _hash = hashlib.sha1(str(files).encode()).hexdigest()\n    zipname += _hash + '.zip'\n\n    if os.path.exists(zipname):\n        return zipname\n\n    zipf = zipfile.ZipFile(zipname, 'w', zipfile.ZIP_DEFLATED)\n\n    for file_to_add in files:\n        if not os.access(file_to_add, os.R_OK):\n            continue\n        if file_to_add in var.config.get('bot', 'ignored_files'):\n            continue\n\n        add_file_as = os.path.basename(file_to_add)\n        zipf.write(file_to_add, add_file_as)\n\n    zipf.close()\n    return zipname\n\n\ndef get_user_ban():\n    res = \"List of ban hash\"\n    for i in var.db.items(\"user_ban\"):\n        res += \"<br/>\" + i[0]\n    return res\n\n\ndef new_release_version(target):\n    if target == \"testing\":\n        r = requests.get(\"https://packages.azlux.fr/botamusique/testing-version\")\n    else:\n        r = requests.get(\"https://packages.azlux.fr/botamusique/version\")\n    v = r.text\n    return v.rstrip()\n\n\ndef fetch_changelog():\n    r = requests.get(\"https://packages.azlux.fr/botamusique/changelog\")\n    c = r.text\n    return c\n\n\ndef check_update(current_version):\n    global log\n    log.debug(\"update: checking for updates...\")\n    new_version = new_release_version(var.config.get('bot', 'target_version'))\n    if version.parse(new_version) > version.parse(current_version):\n        changelog = fetch_changelog()\n        log.info(f\"update: new version {new_version} found, current installed version {current_version}.\")\n        log.info(f\"update: changelog: {changelog}\")\n        changelog = changelog.replace(\"\\n\", \"<br>\")\n        return new_version, changelog\n    else:\n        log.debug(\"update: no new version found.\")\n        return None, None\n\n\ndef update(current_version):\n    global log\n\n    target = var.config.get('bot', 'target_version')\n    new_version = new_release_version(target)\n    msg = \"\"\n    if target == \"git\":\n        msg = \"git install, I do nothing<br/>\"\n\n    elif (target == \"stable\" and version.parse(new_version) > version.parse(current_version)) or \\\n            (target == \"testing\" and version.parse(new_version) != version.parse(current_version)):\n        log.info('update: new version, start updating...')\n        tp = sp.check_output(['/usr/bin/env', 'bash', 'update.sh', target]).decode()\n        log.debug(tp)\n        log.info('update: update pip libraries dependencies')\n        sp.check_output([var.config.get('bot', 'pip3_path'), 'install', '--upgrade', '-r', 'requirements.txt']).decode()\n        msg = \"New version installed, please restart the bot.<br/>\"\n\n    log.info(f'update: starting update {YT_PKG_NAME} via pip3')\n    tp = sp.check_output([var.config.get('bot', 'pip3_path'), 'install', '--upgrade', YT_PKG_NAME]).decode()\n    if f\"Collecting {YT_PKG_NAME}\" in tp.splitlines():\n        msg += \"Update done: \" + tp.split('Successfully installed')[1]\n    else:\n        msg += YT_PKG_NAME.capitalize() + \" is up-to-date\"\n\n    reload(youtube_dl)\n    msg += \"<br/>\" + YT_PKG_NAME.capitalize() + \" reloaded\"\n    return msg\n\n\ndef pipe_no_wait():\n    \"\"\" Generate a non-block pipe used to fetch the STDERR of ffmpeg.\n    \"\"\"\n\n    if platform == \"linux\" or platform == \"linux2\" or platform == \"darwin\" or platform.startswith(\"openbsd\") or platform.startswith(\"freebsd\"):\n        import fcntl\n        import os\n\n        pipe_rd = 0\n        pipe_wd = 0\n\n        if hasattr(os, \"pipe2\"):\n            pipe_rd, pipe_wd = os.pipe2(os.O_NONBLOCK)\n        else:\n            pipe_rd, pipe_wd = os.pipe()\n\n            try:\n                fl = fcntl.fcntl(pipe_rd, fcntl.F_GETFL)\n                fcntl.fcntl(pipe_rd, fcntl.F_SETFL, fl | os.O_NONBLOCK)\n            except:\n                print(sys.exc_info()[1])\n                return None, None\n        return pipe_rd, pipe_wd\n\n    elif platform == \"win32\":\n        # https://stackoverflow.com/questions/34504970/non-blocking-read-on-os-pipe-on-windows\n        import msvcrt\n        import os\n\n        from ctypes import windll, byref, wintypes, WinError, POINTER\n        from ctypes.wintypes import HANDLE, DWORD, BOOL\n\n        pipe_rd, pipe_wd = os.pipe()\n\n        LPDWORD = POINTER(DWORD)\n        PIPE_NOWAIT = wintypes.DWORD(0x00000001)\n        ERROR_NO_DATA = 232\n\n        SetNamedPipeHandleState = windll.kernel32.SetNamedPipeHandleState\n        SetNamedPipeHandleState.argtypes = [HANDLE, LPDWORD, LPDWORD, LPDWORD]\n        SetNamedPipeHandleState.restype = BOOL\n\n        h = msvcrt.get_osfhandle(pipe_rd)\n\n        res = windll.kernel32.SetNamedPipeHandleState(h, byref(PIPE_NOWAIT), None, None)\n        if res == 0:\n            print(WinError())\n            return None, None\n        return pipe_rd, pipe_wd\n\n\nclass Dir(object):\n    def __init__(self, path):\n        self.name = os.path.basename(path.strip('/'))\n        self.fullpath = path\n        self.subdirs = {}\n        self.files = []\n\n    def add_file(self, file):\n        if file.startswith(self.name + '/'):\n            file = file.replace(self.name + '/', '', 1)\n\n        if '/' in file:\n            # This file is in a subdir\n            subdir = file.split('/')[0]\n            if subdir in self.subdirs:\n                self.subdirs[subdir].add_file(file)\n            else:\n                self.subdirs[subdir] = Dir(os.path.join(self.fullpath, subdir))\n                self.subdirs[subdir].add_file(file)\n        else:\n            self.files.append(file)\n        return True\n\n    def get_subdirs(self, path=None):\n        subdirs = []\n        if path and path != '' and path != './':\n            subdir = path.split('/')[0]\n            if subdir in self.subdirs:\n                searchpath = '/'.join(path.split('/')[1::])\n                subdirs = self.subdirs[subdir].get_subdirs(searchpath)\n                subdirs = list(map(lambda subsubdir: os.path.join(subdir, subsubdir), subdirs))\n        else:\n            subdirs = self.subdirs\n\n        return subdirs\n\n    def get_subdirs_recursively(self, path=None):\n        subdirs = []\n        if path and path != '' and path != './':\n            subdir = path.split('/')[0]\n            if subdir in self.subdirs:\n                searchpath = '/'.join(path.split('/')[1::])\n                subdirs = self.subdirs[subdir].get_subdirs_recursively(searchpath)\n        else:\n            subdirs = list(self.subdirs.keys())\n\n            for key, val in self.subdirs.items():\n                subdirs.extend(map(lambda subdir: key + '/' + subdir, val.get_subdirs_recursively()))\n\n        subdirs.sort()\n        return subdirs\n\n    def get_files(self, path=None):\n        files = []\n        if path and path != '' and path != './':\n            subdir = path.split('/')[0]\n            if subdir in self.subdirs:\n                searchpath = '/'.join(path.split('/')[1::])\n                files = self.subdirs[subdir].get_files(searchpath)\n        else:\n            files = self.files\n\n        return files\n\n    def get_files_recursively(self, path=None):\n        files = []\n        if path and path != '' and path != './':\n            subdir = path.split('/')[0]\n            if subdir in self.subdirs:\n                searchpath = '/'.join(path.split('/')[1::])\n                files = self.subdirs[subdir].get_files_recursively(searchpath)\n        else:\n            files = self.files\n\n            for key, val in self.subdirs.items():\n                files.extend(map(lambda file: key + '/' + file, val.get_files_recursively()))\n\n        return files\n\n    def render_text(self, ident=0):\n        print('{}{}/'.format(' ' * (ident * 4), self.name))\n        for key, val in self.subdirs.items():\n            val.render_text(ident + 1)\n        for file in self.files:\n            print('{}{}'.format(' ' * (ident + 1) * 4, file))\n\n\n# Parse the html from the message to get the URL\n\ndef get_url_from_input(string):\n    string = string.strip()\n    if not (string.startswith(\"http\") or string.startswith(\"HTTP\")):\n        res = re.search('href=\"(.+?)\"', string, flags=re.IGNORECASE)\n        if res:\n            string = res.group(1)\n        else:\n            return \"\"\n\n    match = re.search(\"(http|https)://(\\S*)?/(\\S*)\", string, flags=re.IGNORECASE)\n    if match:\n        url = match[1].lower() + \"://\" + match[2].lower() + \"/\" + match[3]\n        # https://github.com/mumble-voip/mumble/issues/4999\n        return html.unescape(url)\n    else:\n        return \"\"\n\n\ndef youtube_search(query):\n    global log\n    import json\n\n    try:\n        cookie_file =  var.config.get('youtube_dl', 'cookie_file')\n        cookie = parse_cookie_file(cookie_file) if cookie_file else {}\n        r = requests.get(\"https://www.youtube.com/results\", cookies=cookie,\n                         params={'search_query': query}, timeout=5)\n        result_json_match = re.findall(r\">var ytInitialData = (.*?);</script>\", r.text)\n\n        if not len(result_json_match):\n            log.error(\"util: can not interpret youtube search web page\")\n            return False\n\n        result_big_json = json.loads(result_json_match[0])\n        results = []\n        try:\n            for item in result_big_json['contents']['twoColumnSearchResultsRenderer']\\\n                    ['primaryContents']['sectionListRenderer']['contents'][0]\\\n                    ['itemSectionRenderer']['contents']:\n                if 'videoRenderer' not in item:\n                    continue\n                video_info = item['videoRenderer']\n                title = video_info['title']['runs'][0]['text']\n                video_id = video_info['videoId']\n                uploader = video_info['ownerText']['runs'][0]['text']\n                results.append([video_id, title, uploader])\n        except (json.JSONDecodeError, KeyError):\n            log.error(\"util: can not interpret youtube search web page\")\n            return False\n\n        return results\n\n    except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError, requests.exceptions.Timeout):\n        error_traceback = traceback.format_exc().split(\"During\")[0]\n        log.error(\"util: youtube query failed with error:\\n %s\" % error_traceback)\n        return False\n\n\ndef get_media_duration(path):\n    command = (\"ffprobe\", \"-v\", \"quiet\", \"-show_entries\", \"format=duration\",\n               \"-of\", \"default=noprint_wrappers=1:nokey=1\", path)\n    process = sp.Popen(command, stdout=sp.PIPE, stderr=sp.PIPE)\n    stdout, stderr = process.communicate()\n\n    try:\n        if not stderr:\n            return float(stdout)\n        else:\n            return 0\n    except ValueError:\n        return 0\n\n\ndef parse_time(human):\n    match = re.search(\"(?:(\\d\\d):)?(?:(\\d\\d):)?(\\d+(?:\\.\\d*)?)\", human, flags=re.IGNORECASE)\n    if match:\n        if match[1] is None and match[2] is None:\n            return float(match[3])\n        elif match[2] is None:\n            return float(match[3]) + 60 * int(match[1])\n        else:\n            return float(match[3]) + 60 * int(match[2]) + 3600 * int(match[1])\n    else:\n        raise ValueError(\"Invalid time string given.\")\n\n\ndef format_time(seconds):\n    hours = seconds // 3600\n    seconds = seconds % 3600\n    minutes = seconds // 60\n    seconds = seconds % 60\n    return f\"{hours:d}:{minutes:02d}:{seconds:02d}\"\n\n\ndef parse_file_size(human):\n    units = {\"B\": 1, \"KB\": 1024, \"MB\": 1024 * 1024, \"GB\": 1024 * 1024 * 1024, \"TB\": 1024 * 1024 * 1024 * 1024,\n             \"K\": 1024, \"M\": 1024 * 1024, \"G\": 1024 * 1024 * 1024, \"T\": 1024 * 1024 * 1024 * 1024}\n    match = re.search(\"(\\d+(?:\\.\\d*)?)\\s*([A-Za-z]+)\", human, flags=re.IGNORECASE)\n    if match:\n        num = float(match[1])\n        unit = match[2].upper()\n        if unit in units:\n            return int(num * units[unit])\n\n    raise ValueError(\"Invalid file size given.\")\n\n\ndef get_salted_password_hash(password):\n    salt = os.urandom(10)\n    hashed = hashlib.pbkdf2_hmac('sha1', password.encode(\"utf-8\"), salt, 100000)\n\n    return hashed.hex(), salt.hex()\n\n\ndef verify_password(password, salted_hash, salt):\n    hashed = hashlib.pbkdf2_hmac('sha1', password.encode(\"utf-8\"), bytearray.fromhex(salt), 100000)\n    if hashed.hex() == salted_hash:\n        return True\n    return False\n\n\ndef get_supported_language():\n    root_dir = os.path.dirname(__file__)\n    lang_files = os.listdir(os.path.join(root_dir, 'lang'))\n    lang_list = []\n    for lang_file in lang_files:\n        match = re.search(\"([a-z]{2}_[A-Z]{2})\\.json\", lang_file)\n        if match:\n            lang_list.append(match[1])\n\n    return lang_list\n\n\ndef set_logging_formatter(handler: logging.Handler, logging_level):\n    if logging_level == logging.DEBUG:\n        formatter = logging.Formatter(\n            \"[%(asctime)s] > [%(threadName)s] > \"\n            \"[%(filename)s:%(lineno)d] %(message)s\"\n        )\n    else:\n        formatter = logging.Formatter(\n            '[%(asctime)s %(levelname)s] %(message)s', \"%b %d %H:%M:%S\")\n\n    handler.setFormatter(formatter)\n\n\ndef get_snapshot_version():\n    import subprocess\n    wd = os.getcwd()\n    root_dir = os.path.dirname(__file__)\n    os.chdir(root_dir)\n\n    ver = \"unknown\"\n    if os.path.exists(os.path.join(root_dir, \".git\")):\n        try:\n            ret = subprocess.check_output([\"git\", \"describe\", \"--tags\"]).strip()\n            ver = ret.decode(\"utf-8\")\n        except (FileNotFoundError, subprocess.CalledProcessError):\n            try:\n                with open(os.path.join(root_dir, \".git/refs/heads/master\")) as f:\n                    ver = \"g\" + f.read()[:7]\n            except FileNotFoundError:\n                pass\n\n    os.chdir(wd)\n    return ver\n\n\nclass LoggerIOWrapper(io.TextIOWrapper):\n    def __init__(self, logger: logging.Logger, logging_level, fallback_io_buffer):\n        super().__init__(fallback_io_buffer, write_through=True)\n        self.logger = logger\n        self.logging_level = logging_level\n\n    def write(self, text):\n        if isinstance(text, bytes):\n            msg = text.decode('utf-8').rstrip()\n            self.logger.log(self.logging_level, msg)\n            super().write(msg + \"\\n\")\n        else:\n            self.logger.log(self.logging_level, text.rstrip())\n            super().write(text + \"\\n\")\n\n\nclass VolumeHelper:\n    def __init__(self, plain_volume=0, ducking_plain_volume=0):\n        self.plain_volume_set = 0\n        self.plain_ducking_volume_set = 0\n        self.volume_set = 0\n        self.ducking_volume_set = 0\n\n        self.real_volume = 0\n\n        self.set_volume(plain_volume)\n        self.set_ducking_volume(ducking_plain_volume)\n\n    def set_volume(self, plain_volume):\n        self.volume_set = self._convert_volume(plain_volume)\n        self.plain_volume_set = plain_volume\n\n    def set_ducking_volume(self, plain_volume):\n        self.ducking_volume_set = self._convert_volume(plain_volume)\n        self.plain_ducking_volume_set = plain_volume\n\n    def _convert_volume(self, volume):\n        if volume == 0:\n            return 0\n\n        # convert input of 0~1 into -35~5 dB\n        dB = -35 + volume * 40\n\n        # Some dirty trick to stretch the function, to make to be 0 when input is -35 dB\n        return (10 ** (dB / 20) - 10 ** (-35 / 20)) / (1 - 10 ** (-35 / 20))\n\n\ndef get_size_folder(path):\n    global log\n\n    folder_size = 0\n    for (path, dirs, files) in os.walk(path):\n        for file in files:\n            filename = os.path.join(path, file)\n            try:\n                folder_size += os.path.getsize(filename)\n            except (FileNotFoundError, OSError):\n                continue\n    return int(folder_size / (1024 * 1024))\n\n\ndef clear_tmp_folder(path, size):\n    global log\n\n    if size == -1:\n        return\n    elif size == 0:\n        for (path, dirs, files) in os.walk(path):\n            for file in files:\n                filename = os.path.join(path, file)\n                try:\n                    os.remove(filename)\n                except (FileNotFoundError, OSError):\n                    continue\n    else:\n        if get_size_folder(path=path) > size:\n            all_files = \"\"\n            for (path, dirs, files) in os.walk(path):\n                all_files = [os.path.join(path, file) for file in files]\n                # exclude invalid symlinks (linux)\n                all_files = [file for file in all_files if os.path.exists(file)]\n                all_files.sort(key=lambda x: os.path.getmtime(x))\n            size_tp = 0\n            for idx, file in enumerate(all_files):\n                size_tp += os.path.getsize(file)\n                if int(size_tp / (1024 * 1024)) > size:\n                    log.info(\"Cleaning tmp folder\")\n                    to_remove = all_files[:idx]\n                    print(to_remove)\n                    for f in to_remove:\n                        log.debug(\"Removing \" + f)\n                        try:\n                            os.remove(os.path.join(path, f))\n                        except (FileNotFoundError, OSError):\n                            continue\n                    return\n\n\ndef check_extra_config(config, template):\n    extra = []\n\n    for key in config.sections():\n        if key in ['radio']:\n            continue\n        for opt in config.options(key):\n            if not template.has_option(key, opt):\n                extra.append((key, opt))\n\n    return extra\n\n\ndef parse_cookie_file(cookiefile):\n    # https://stackoverflow.com/a/54659484/1584825\n\n    cookies = {}\n    with open (cookiefile, 'r') as fp:\n        for line in fp:\n            if not re.match(r'^#', line):\n                lineFields = line.strip().split('\\t')\n                cookies[lineFields[5]] = lineFields[6]\n    return cookies\n"
  },
  {
    "path": "variables.py",
    "content": "from typing import Type, TYPE_CHECKING\n\nif TYPE_CHECKING:\n    import mumbleBot\n    import media.playlist\n    import media.cache\n    import database\n\nbot: 'mumbleBot.MumbleBot' = None\nplaylist: 'media.playlist.BasePlaylist' = None\ncache: 'media.cache.MusicCache' = None\n\nuser = \"\"\nis_proxified = False\n\nsettings_db_path = None\nmusic_db_path = None\ndb = None\nmusic_db: 'database.MusicDatabase' = None\nconfig: 'database.SettingsDatabase' = None\n\nbot_logger = None\n\nmusic_folder = \"\"\ntmp_folder = \"\"\n\nlanguage = \"\"\n"
  },
  {
    "path": "web/.editorconfig",
    "content": "[*]\ncharset = utf-8\ninsert_final_newline = true\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = true\nquote_type = single\n\n[*.json]\nquote_type = double\n"
  },
  {
    "path": "web/.eslintrc.json",
    "content": "{\n  \"parser\": \"@babel/eslint-parser\",\n  \"env\": {\n    \"browser\": true,\n    \"es6\": true,\n    \"es2017\": true,\n    \"es2020\": true,\n    \"es2021\": true,\n    \"jquery\": true\n  },\n  \"plugins\": [\n    \"@babel\",\n    \"import\",\n    \"jsdoc\",\n    \"jquery\"\n  ],\n  \"extends\": [\n    \"eslint:recommended\",\n    \"plugin:import/errors\",\n    \"plugin:import/warnings\",\n    \"plugin:jsdoc/recommended\",\n    \"plugin:jquery/deprecated\"\n  ],\n  \"rules\": {\n    \"max-len\": [\"warn\", {\n      \"code\": 120\n    }],\n    \"linebreak-style\": \"off\",\n    \"jsdoc/require-jsdoc\": \"off\",\n    \"import/unambiguous\": \"error\",\n    \"import/no-commonjs\": \"error\",\n    \"import/no-amd\": \"error\",\n    \"import/no-nodejs-modules\": \"error\",\n    \"import/no-deprecated\": \"error\",\n    \"import/extensions\": [\"error\", \"always\"],\n    \"import/no-unresolved\": [\"error\", {\n      \"commonjs\": true\n    }]\n  }\n}\n"
  },
  {
    "path": "web/.gitattributes",
    "content": "package-lock.json text eol=lf\n"
  },
  {
    "path": "web/.gitignore",
    "content": "!*\nnode_modules/\n"
  },
  {
    "path": "web/babel.config.json",
    "content": "{\n  \"plugins\": [\n    \"@babel/plugin-proposal-class-properties\"\n  ]\n}\n"
  },
  {
    "path": "web/js/app.mjs",
    "content": "import {library, dom} from '@fortawesome/fontawesome-svg-core/index.es.js';\nimport {\n  faTimesCircle, faPlus, faCheck, faUpload, faTimes, faTrash, faPlay, faPause, faFastForward, faPlayCircle, faLightbulb,\n  faTrashAlt, faDownload, faSyncAlt, faEdit, faVolumeUp, faVolumeDown, faRobot, faRedo, faRandom, faTasks\n} from '@fortawesome/free-solid-svg-icons/index.es.js';\nimport {faFileAlt} from '@fortawesome/free-regular-svg-icons/index.es.js';\n\nlibrary.add(\n  // Solid\n  faTimesCircle, faPlus, faCheck, faUpload, faTimes, faTrash, faPlay, faPause, faFastForward, faPlayCircle, faLightbulb,\n  faTrashAlt, faDownload, faSyncAlt, faEdit, faVolumeUp, faVolumeDown, faRobot, faRedo, faRandom, faTasks,\n  // Regular\n  faFileAlt\n);\n\n// Old application code\nimport './main.mjs';\n\n// New application code\nimport Theme from './lib/theme.mjs';\n\ndocument.addEventListener('DOMContentLoaded', () => {\n  Theme.init();\n\n  // Replace any existing <i> tags with <svg> and set up a MutationObserver to\n  // continue doing this as the DOM changes.\n  dom.watch();\n\n  document.getElementById('theme-switch-btn').addEventListener('click', () => {\n    Theme.swap();\n  });\n});\n\n"
  },
  {
    "path": "web/js/lib/text.mjs",
    "content": "import {validateString, validateNumber} from './type.mjs';\n\n/**\n * Truncate string length by characters.\n *\n * @param {string} text String to format.\n * @param {number} limit Maximum number of characters in resulting string.\n * @param {string} ending Ending to use if string is trucated.\n *\n * @returns {string} Formatted string.\n */\nexport function limitChars(text, limit = 50, ending = '...') {\n  validateString(text);\n  validateNumber(limit);\n  validateString(ending);\n\n  // Check if string is already below limit\n  if (text.length <= limit) {\n    return text;\n  }\n\n  // Limit string length by characters\n  return text.substring(0, limit - ending.length) + ending;\n}\n\n/**\n * Truncate string length by words.\n *\n * @param {string} text String to format.\n * @param {number} limit Maximum number of words in resulting string.\n * @param {string} ending Ending to use if string is trucated.\n *\n * @returns {string} Formatted string.\n */\nexport function limitWords(text, limit = 10, ending = '...') {\n  validateString(text);\n  validateNumber(limit);\n  validateString(ending);\n\n  // Limit string length by words\n  return text.split(' ').splice(0, limit).join(' ') + ending;\n}\n"
  },
  {
    "path": "web/js/lib/theme.mjs",
    "content": "export default class {\n    /**\n     * @property {boolean} dark Interal state for dark theme activation.\n     * @private\n     */\n    static #dark = false;\n\n    /**\n     * Inialize the theme class.\n     */\n    static init() {\n      // Check LocalStorage for dark theme selection\n      if (localStorage.getItem('darkTheme') === 'true') {\n        // Update page theme\n        this.set(true);\n      }\n    }\n\n    /**\n     * Set page theme and update local storage variable.\n     *\n     * @param {boolean} dark Whether to activate dark theme.\n     */\n    static set(dark = false) {\n      // Swap CSS to selected theme\n      document.getElementById('pagestyle')\n          .setAttribute('href', 'static/css/' + (dark ? 'dark' : 'main') + '.css');\n\n      // Update local storage\n      localStorage.setItem('darkTheme', dark);\n\n      // Update internal state\n      this.#dark = dark;\n    }\n\n    /**\n     * Swap page theme.\n     */\n    static swap() {\n      this.set(!this.#dark);\n    }\n}\n"
  },
  {
    "path": "web/js/lib/type.mjs",
    "content": "/**\n * Checks if `value` is the type `Object` excluding `Function` and `null`\n *\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an object, otherwise `false`.\n */\nexport function isObject(value) {\n  return (Object.prototype.toString.call(value) === '[object Object]');\n}\n\n/**\n * Checks if `value` is the type `string`\n *\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a string, otherwise `false`.\n */\nexport function isString(value) {\n  return (typeof value === 'string');\n}\n\n/**\n * Checks if `value` is the type `number`\n *\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a number, otherwise `false`.\n */\nexport function isNumber(value) {\n  return (typeof value === 'number');\n}\n\n/**\n * Validate parameter is of type object.\n *\n * @param {string} value Variable to validate.\n * @throws Error if not an object.\n */\nexport function validateObject(value) {\n  if (!isObject(value)) {\n    throw new TypeError('Parameter \"value\" must be of type object.');\n  }\n}\n\n/**\n * Validate parameter is of type string.\n *\n * @param {string} value Variable to validate.\n * @throws Error if not an string.\n */\nexport function validateString(value) {\n  if (!isString(value)) {\n    throw new TypeError('Parameter \"value\" must be of type string.');\n  }\n}\n\n/**\n * Validate parameter is of type number.\n *\n * @param {number} value Variable to validate.\n * @throws Error if not an number.\n */\nexport function validateNumber(value) {\n  if (!isNumber(value)) {\n    throw new TypeError('Parameter \"value\" must be of type number.');\n  }\n}\n"
  },
  {
    "path": "web/js/lib/util.mjs",
    "content": "export function isOverflown(element) {\n  return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;\n}\n\nexport function hash(string) {\n  if (typeof string != 'string') return 0;\n  let hash = 0;\n  if (string.length === 0) {\n    return hash;\n  }\n  for (let i = 0; i < string.length; i++) {\n    const char = string.charCodeAt(i);\n    hash = ((hash<<5)-hash)+char;\n    hash = hash & hash; // Convert to 32bit integer\n  }\n  return hash;\n}\n\nexport function getColor(string) {\n  const num = hash(string) % 8;\n\n  switch (num) {\n    case 0:\n      return 'primary';\n    case 1:\n      return 'secondary';\n    case 2:\n      return 'success';\n    case 3:\n      return 'danger';\n    case 4:\n      return 'warning';\n    case 5:\n      return 'info';\n    case 6:\n      return 'light';\n    case 7:\n      return 'dark';\n  }\n}\n\nexport function setProgressBar(bar, progress, text = '') {\n  const progPos = (-1 * (1 - progress) * bar.scrollWidth).toString();\n  const progStr = (progress * 100).toString();\n  bar.setAttribute('aria-valuenow', progStr);\n  bar.style.transform = 'translateX(' + progPos + 'px)';\n  bar.textContent = text;\n}\n\nexport function secondsToStr(seconds) {\n  seconds = Math.floor(seconds);\n  const mins = Math.floor(seconds / 60);\n  const secs = seconds % 60;\n  return ('00' + mins).slice(-2) + ':' + ('00' + secs).slice(-2);\n}\n"
  },
  {
    "path": "web/js/main.mjs",
    "content": "import 'jquery/src/jquery.js';\nimport 'jquery-migrate/src/migrate.js';\nimport Popper from 'popper.js/dist/esm/popper.js';\nimport {\n  Modal,\n  Toast,\n  Tooltip,\n} from 'bootstrap/js/src/index.js';\nimport {\n  getColor,\n  isOverflown,\n  setProgressBar,\n  secondsToStr,\n} from './lib/util.mjs';\nimport {limitChars} from './lib/text.mjs';\n\n$('#uploadSelectFile').on('change', function() {\n  // get the file name\n  const fileName = $(this).val().replace('C:\\\\fakepath\\\\', ' ');\n  // replace the \"Choose a file\" label\n  $(this).next('.custom-file-label').html(fileName);\n});\n\n\n// ----------------------\n// ------ Playlist ------\n// ----------------------\n\nconst pl_item_template = $('.playlist-item-template');\nconst pl_id_element = $('.playlist-item-id');\nconst pl_index_element = $('.playlist-item-index');\nconst pl_title_element = $('.playlist-item-title');\nconst pl_artist_element = $('.playlist-item-artist');\nconst pl_thumb_element = $('.playlist-item-thumbnail');\nconst pl_type_element = $('.playlist-item-type');\nconst pl_path_element = $('.playlist-item-path');\n\nconst pl_tag_edit_element = $('.playlist-item-edit');\n\nconst notag_element = $('.library-item-notag'); // these elements are shared with library\nconst tag_element = $('.library-item-tag');\n\nconst addTagModal = new Modal(document.getElementById('addTagModal'));\n\nconst playlist_loading = $('#playlist-loading');\nconst playlist_table = $('#playlist-table');\nconst playlist_empty = $('#playlist-empty');\nconst playlist_expand = $('.playlist-expand');\n\nlet playlist_items = null;\n\nlet playlist_ver = 0;\nlet playlist_current_index = 0;\n\nlet playlist_range_from = 0;\nlet playlist_range_to = 0;\n\nlet last_volume = 0;\n\nlet playing = false;\n\nconst playPauseBtn = $('#play-pause-btn');\nconst fastForwardBtn = $('#fast-forward-btn');\nconst volumeSlider = document.getElementById('volume-slider');\n\nconst playModeBtns = {\n  'one-shot': $('#one-shot-mode-btn'),\n  'random': $('#random-mode-btn'),\n  'repeat': $('#repeat-mode-btn'),\n  'autoplay': $('#autoplay-mode-btn'),\n};\nconst playModeIcon = {\n  'one-shot': 'fa-tasks',\n  'random': 'fa-random',\n  'repeat': 'fa-redo',\n  'autoplay': 'fa-robot',\n};\n\nplayPauseBtn.on('click', togglePlayPause);\n\nfastForwardBtn.on('click', () => {\n  request('post', {\n    action: 'next',\n  });\n});\n\ndocument.getElementById('clear-playlist-btn').addEventListener('click', () => {\n  request('post', {action: 'clear'});\n});\n\n// eslint-disable-next-line guard-for-in\nfor (const playMode in playModeBtns) {\n  playModeBtns[playMode].on('click', () => {\n    changePlayMode(playMode);\n  });\n}\n\nfunction request(_url, _data, refresh = false) {\n  console.log(_data);\n  $.ajax({\n    type: 'POST',\n    url: _url,\n    data: _data,\n    statusCode: {\n      200: function(data) {\n        if (data.ver !== playlist_ver) {\n          checkForPlaylistUpdate();\n        }\n        updateControls(data.empty, data.play, data.mode, data.volume);\n        updatePlayerPlayhead(data.playhead);\n      },\n      403: function() {\n        location.reload(true);\n      },\n    },\n  });\n  if (refresh) {\n    location.reload(true);\n  }\n}\n\nfunction addPlaylistItem(item) {\n  pl_id_element.val(item.id);\n  pl_index_element.html(item.index + 1);\n  pl_title_element.html(item.title);\n  pl_artist_element.html(item.artist);\n  pl_thumb_element.attr('src', item.thumbnail);\n  pl_thumb_element.attr('alt', limitChars(item.title));\n  pl_type_element.html(item.type);\n  pl_path_element.html(item.path);\n\n  const item_copy = pl_item_template.clone();\n  item_copy.attr('id', 'playlist-item-' + item.index);\n  item_copy.addClass('playlist-item').removeClass('d-none');\n\n  const tags = item_copy.find('.playlist-item-tags');\n  tags.empty();\n\n  const tag_edit_copy = pl_tag_edit_element.clone();\n  tag_edit_copy.click(function() {\n    addTagModalShow(item.id, item.title, item.tags);\n  });\n  tag_edit_copy.appendTo(tags);\n\n  if (item.tags.length > 0) {\n    item.tags.forEach(function(tag_tuple) {\n      const tag_copy = tag_element.clone();\n      tag_copy.html(tag_tuple[0]);\n      tag_copy.addClass('badge-' + tag_tuple[1]);\n      tag_copy.appendTo(tags);\n    });\n  } else {\n    const tag_copy = notag_element.clone();\n    tag_copy.appendTo(tags);\n  }\n\n  item_copy.appendTo(playlist_table);\n}\n\nfunction displayPlaylist(data) {\n  playlist_table.animate({\n    opacity: 0,\n  }, 200, function() {\n    playlist_loading.hide();\n    $('.playlist-item').remove();\n    const items = data.items;\n    const length = data.length;\n    if (items.length === 0) {\n      playlist_empty.removeClass('d-none');\n      playlist_table.animate({opacity: 1}, 200);\n      return;\n    }\n    playlist_items = {};\n    for (const i in items) {\n      playlist_items[items[i].index] = items[i];\n    }\n    const start_from = data.start_from;\n    playlist_range_from = start_from;\n    playlist_range_to = start_from + items.length - 1;\n\n    if (items.length < length && start_from > 0) {\n      let _from = start_from - 5;\n      _from = _from > 0 ? _from : 0;\n      const _to = start_from - 1;\n      if (_to > 0) {\n        insertExpandPrompt(_from, start_from + length - 1, _from, _to, length);\n      }\n    }\n\n    items.forEach(\n        function(item) {\n          addPlaylistItem(item);\n        },\n    );\n\n    if (items.length < length && start_from + items.length < length) {\n      const _from = start_from + items.length;\n      let _to = start_from + items.length - 1 + 10;\n      _to = _to < length - 1 ? _to : length - 1;\n      if (start_from + items.length < _to) {\n        insertExpandPrompt(start_from, _to, _from, _to, length);\n      }\n    }\n\n    displayActiveItem(data.current_index);\n    updatePlayerInfo(playlist_items[data.current_index]);\n    bindPlaylistEvent();\n    playlist_table.animate({opacity: 1}, 200);\n  });\n}\n\nfunction displayActiveItem(current_index) {\n  $('.playlist-item').removeClass('table-active');\n  $('#playlist-item-' + current_index).addClass('table-active');\n}\n\nfunction insertExpandPrompt(real_from, real_to, display_from, display_to, total_length) {\n  const expand_copy = playlist_expand.clone();\n  expand_copy.addClass('playlist-item');\n  expand_copy.removeClass('d-none');\n  if (display_from !== display_to) {\n    expand_copy.find('.playlist-expand-item-range').html((display_from + 1) + '~' + (display_to + 1) +\n      ' of ' + (total_length) + ' items');\n  } else {\n    expand_copy.find('.playlist-expand-item-range').html(display_from + ' of ' + (total_length) + ' items');\n  }\n\n  expand_copy.addClass('playlist-item');\n  expand_copy.appendTo(playlist_table);\n  expand_copy.click(function() {\n    playlist_range_from = real_from;\n    playlist_range_to = real_to;\n    updatePlaylist();\n  });\n}\n\nfunction updatePlaylist() {\n  playlist_table.animate({\n    opacity: 0,\n  }, 200, function() {\n    playlist_empty.addClass('d-none');\n    playlist_loading.show();\n    playlist_table.find('.playlist-item').css('opacity', 0);\n    let data = {};\n    if (!(playlist_range_from === 0 && playlist_range_to === 0)) {\n      data = {\n        range_from: playlist_range_from,\n        range_to: playlist_range_to,\n      };\n    }\n    $.ajax({\n      type: 'GET',\n      url: 'playlist',\n      data: data,\n      statusCode: {\n        200: displayPlaylist,\n      },\n    });\n    playlist_table.animate({\n      opacity: 1,\n    }, 200);\n  });\n}\n\nfunction checkForPlaylistUpdate() {\n  $.ajax({\n    type: 'POST',\n    url: 'post',\n    statusCode: {\n      200: function(data) {\n        if (data.ver !== playlist_ver) {\n          playlist_ver = data.ver;\n          playlist_range_from = 0;\n          playlist_range_to = 0;\n          updatePlaylist();\n        }\n        if (data.current_index !== playlist_current_index) {\n          if (data.current_index !== -1) {\n            if ((data.current_index > playlist_range_to || data.current_index < playlist_range_from)) {\n              playlist_range_from = 0;\n              playlist_range_to = 0;\n              updatePlaylist();\n            } else {\n              playlist_current_index = data.current_index;\n              updatePlayerInfo(playlist_items[data.current_index]);\n              displayActiveItem(data.current_index);\n            }\n          }\n        }\n        updateControls(data.empty, data.play, data.mode, data.volume);\n        if (!data.empty) {\n          updatePlayerPlayhead(data.playhead);\n        }\n      },\n    },\n  });\n}\n\nfunction bindPlaylistEvent() {\n  $('.playlist-item-play').unbind().click(\n      function(e) {\n        request('post', {\n          'play_music': ($(e.currentTarget).parent().parent().parent().find('.playlist-item-index').html() - 1),\n        });\n      },\n  );\n  $('.playlist-item-trash').unbind().click(\n      function(e) {\n        request('post', {\n          'delete_music': ($(e.currentTarget).parent().parent().parent().find('.playlist-item-index').html() - 1),\n        });\n      },\n  );\n}\n\nfunction updateControls(empty, play, mode, volume) {\n  updatePlayerControls(play, empty);\n  if (empty) {\n    playPauseBtn.prop('disabled', true);\n    fastForwardBtn.prop('disabled', true);\n  } else {\n    playPauseBtn.prop('disabled', false);\n    fastForwardBtn.prop('disabled', false);\n    if (play) {\n      playing = true;\n      playPauseBtn.find('[data-fa-i2svg]').removeClass('fa-play').addClass('fa-pause');\n      // PR #180: Since this button changes behavior dynamically, we change its\n      // ARIA labels in JS instead of only adding them statically in the HTML\n      playPauseBtn.attr('aria-label', 'Pause');\n    } else {\n      playing = false;\n      playPauseBtn.find('[data-fa-i2svg]').removeClass('fa-pause').addClass('fa-play');\n      // PR #180: Since this button changes behavior dynamically, we change its\n      // ARIA labels in JS instead of only adding them statically in the HTML\n      playPauseBtn.attr('aria-label', 'Play');\n    }\n  }\n\n  for (const otherMode of Object.values(playModeBtns)) {\n    otherMode.removeClass('active');\n  }\n  playModeBtns[mode].addClass('active');\n\n  const playModeIndicator = $('#modeIndicator');\n  for (const icon_class of Object.values(playModeIcon)) {\n    playModeIndicator.removeClass(icon_class);\n  }\n  playModeIndicator.addClass(playModeIcon[mode]);\n\n  if (volume !== last_volume) {\n    last_volume = volume;\n    if (volume > 1) {\n      volumeSlider.value = 1;\n    } else if (volume < 0) {\n      volumeSlider.value = 0;\n    } else {\n      volumeSlider.value = volume;\n    }\n  }\n}\n\nfunction togglePlayPause() {\n  if (playing) {\n    request('post', {\n      action: 'pause',\n    });\n  } else {\n    request('post', {\n      action: 'resume',\n    });\n  }\n}\n\nfunction changePlayMode(mode) {\n  request('post', {\n    action: mode,\n  });\n}\n\n\n// ---------------------\n// ------ Browser ------\n// ---------------------\n\nconst filters = {\n  file: $('#filter-type-file'),\n  url: $('#filter-type-url'),\n  radio: $('#filter-type-radio'),\n};\nconst filter_dir = $('#filter-dir');\nconst filter_keywords = $('#filter-keywords');\n\n// eslint-disable-next-line guard-for-in\nfor (const filter in filters) {\n  filters[filter].on('click', (e) => {\n    setFilterType(e, filter);\n  });\n}\n\nfunction setFilterType(event, type) {\n  event.preventDefault();\n\n  if (filters[type].hasClass('active')) {\n    filters[type].removeClass('active btn-primary').addClass('btn-secondary');\n    filters[type].find('input[type=radio]').removeAttr('checked');\n  } else {\n    filters[type].removeClass('btn-secondary').addClass('active btn-primary');\n    filters[type].find('input[type=radio]').attr('checked', 'checked');\n  }\n\n  if (type === 'file') {\n    filter_dir.prop('disabled', !filters['file'].hasClass('active'));\n  }\n\n  updateResults();\n}\n\n\nfilter_dir.change(function() {\n  updateResults();\n});\nfilter_keywords.change(function() {\n  updateResults();\n});\n\nconst item_template = $('#library-item');\n\nfunction bindLibraryResultEvent() {\n  $('.library-thumb-col').unbind().hover(\n      function(e) {\n        $(e.currentTarget).find('.library-thumb-grp').addClass('library-thumb-grp-hover');\n      },\n      function(e) {\n        $(e.currentTarget).find('.library-thumb-grp').removeClass('library-thumb-grp-hover');\n      },\n  );\n\n  $('.library-info-title').unbind().hover(\n      function(e) {\n        $(e.currentTarget).parent().find('.library-thumb-grp').addClass('library-thumb-grp-hover');\n      },\n      function(e) {\n        $(e.currentTarget).parent().find('.library-thumb-grp').removeClass('library-thumb-grp-hover');\n      },\n  );\n\n  $('.library-item-play').unbind().click(\n      function(e) {\n        request('post', {\n          'add_item_at_once': $(e.currentTarget).parent().parent().parent().find('.library-item-id').val(),\n        });\n      },\n  );\n\n  $('.library-item-trash').unbind().click(\n      function(e) {\n        request('post', {\n          'delete_item_from_library': $(e.currentTarget).parent().parent().find('.library-item-id').val(),\n        });\n        updateResults(active_page);\n      },\n  );\n\n  $('.library-item-download').unbind().click(\n      function(e) {\n        const id = $(e.currentTarget).parent().parent().find('.library-item-id').val();\n        // window.open('/download?id=' + id);\n        downloadId(id);\n      },\n  );\n\n  $('.library-item-add-next').unbind().click(\n      function(e) {\n        const id = $(e.currentTarget).parent().parent().find('.library-item-id').val();\n        request('post', {\n          'add_item_next': id,\n        });\n      },\n  );\n\n  $('.library-item-add-bottom').unbind().click(\n      function(e) {\n        const id = $(e.currentTarget).parent().parent().find('.library-item-id').val();\n        request('post', {\n          'add_item_bottom': id,\n        });\n      },\n  );\n}\n\nconst lib_filter_tag_group = $('#filter-tags');\nconst lib_filter_tag_element = $('.filter-tag');\n\nconst lib_group = $('#library-group');\nconst id_element = $('.library-item-id');\nconst title_element = $('.library-item-title');\nconst artist_element = $('.library-item-artist');\nconst thumb_element = $('.library-item-thumb');\nconst type_element = $('.library-item-type');\nconst path_element = $('.library-item-path');\n\nconst tag_edit_element = $('.library-item-edit');\n// var notag_element = $(\".library-item-notag\");\n// var tag_element = $(\".library-item-tag\");\n\nconst library_tags = [];\n\nfunction updateLibraryControls() {\n  $.ajax({\n    type: 'GET',\n    url: 'library/info',\n    statusCode: {\n      200: displayLibraryControls,\n      403: function() {\n        location.reload(true);\n      },\n    },\n  });\n}\n\nfunction displayLibraryControls(data) {\n  $('#maxUploadFileSize').val(data.max_upload_file_size);\n  if (data.upload_enabled) {\n    $('#uploadDisabled').val('false');\n    $('#upload').show();\n  } else {\n    $('#uploadDisabled').val('true');\n    $('#upload').hide();\n  }\n\n  if (data.delete_allowed) {\n    $('#deleteAllowed').val('true');\n  } else {\n    $('#deleteAllowed').val('false');\n    $('.library-delete').remove();\n  }\n\n  const dataList = $('#upload-target-dirs');\n  const dirs = [];\n  filter_dir.find('option').each(function(i, dir_element){\n    dirs.push(dir_element.value);\n    });\n  if (data.dirs.length > 0) {\n    console.log(data.dirs);\n    data.dirs.forEach(function(dir) {\n      if(!dirs.includes(dir)) {\n        $('<option value=\"' + dir + '\">' + dir + '</option>').appendTo(filter_dir);\n        $('<option value=\"' + dir + '\">').appendTo(dataList);\n      }\n    });\n  }\n\n  // ----- Tag filters -----\n  const tags = [];\n  const tags_dict = {};\n  $('.filter-tag').each(function(i, tag_element) {\n    tags_dict[tag_element.innerHTML] = tag_element;\n    tags.push(tag_element.innerHTML);\n  });\n  if (data.tags.length > 0) {\n    for (const tag of data.tags) {\n      if (tags.includes(tag)) {\n        const index = tags.indexOf(tag);\n        tags.splice(index, 1);\n      } else {\n        const tag_copy = lib_filter_tag_element.clone();\n        tag_copy.html(tag);\n        tag_copy.addClass('badge-' + getColor(tag));\n        tag_copy.appendTo(lib_filter_tag_group);\n        // Bind Event\n        tag_copy.click(function(e) {\n          const tag = $(e.currentTarget);\n          if (!tag.hasClass('tag-clicked')) {\n            tag.addClass('tag-clicked');\n            tag.removeClass('tag-unclicked');\n          } else {\n            tag.addClass('tag-unclicked');\n            tag.removeClass('tag-clicked');\n          }\n          updateResults();\n        });\n      }\n    }\n    for (const tag of tags) {\n      tags_dict[tag].remove();\n    }\n  } else {\n    $('.filter-tag').remove();\n  }\n}\n\nfunction addResultItem(item) {\n  id_element.val(item.id);\n  title_element.html(item.title);\n  artist_element.html(item.artist ? ('- ' + item.artist) : '');\n  thumb_element.attr('src', item.thumb);\n  thumb_element.attr('alt', limitChars(item.title));\n  type_element.html('[' + item.type + ']');\n  path_element.html(item.path);\n\n  const item_copy = item_template.clone();\n  item_copy.addClass('library-item-active');\n\n  const tags = item_copy.find('.library-item-tags');\n  tags.empty();\n\n  const tag_edit_copy = tag_edit_element.clone();\n  tag_edit_copy.click(function() {\n    addTagModalShow(item.id, item.title, item.tags);\n  });\n  tag_edit_copy.appendTo(tags);\n\n  if (item.tags.length > 0) {\n    item.tags.forEach(function(tag_tuple) {\n      const tag_copy = tag_element.clone();\n      tag_copy.html(tag_tuple[0]);\n      tag_copy.addClass('badge-' + tag_tuple[1]);\n      tag_copy.appendTo(tags);\n    });\n  } else {\n    const tag_copy = notag_element.clone();\n    tag_copy.appendTo(tags);\n  }\n\n  item_copy.appendTo(lib_group);\n  item_copy.show();\n}\n\nfunction getFilters(dest_page = 1) {\n  const tags = $('.tag-clicked');\n  const tags_list = [];\n  tags.each(function(index, tag) {\n    tags_list.push(tag.innerHTML);\n  });\n\n  const filter_types = [];\n  for (const filter in filters) {\n    if (filters[filter].hasClass('active')) {\n      filter_types.push(filter);\n    }\n  }\n\n  return {\n    type: filter_types.join(','),\n    dir: filter_dir.val(),\n    tags: tags_list.join(','),\n    keywords: filter_keywords.val(),\n    page: dest_page,\n  };\n}\n\nconst lib_loading = $('#library-item-loading');\nconst lib_empty = $('#library-item-empty');\nlet active_page = 1;\n\nfunction updateResults(dest_page = 1) {\n  active_page = dest_page;\n  const data = getFilters(dest_page);\n  data.action = 'query';\n\n  lib_group.animate({\n    opacity: 0,\n  }, 200, function() {\n    $.ajax({\n      type: 'POST',\n      url: 'library',\n      data: data,\n      statusCode: {\n        200: processResults,\n        403: function() {\n          location.reload(true);\n        },\n      },\n    });\n\n    $('.library-item-active').remove();\n    lib_empty.hide();\n    lib_loading.show();\n    lib_group.animate({\n      opacity: 1,\n    }, 200);\n  });\n\n  updateLibraryControls();\n}\n\nconst download_form = $('#download-form');\nconst download_id = download_form.find('input[name=\\'id\\']');\nconst download_type = download_form.find('input[name=\\'type\\']');\nconst download_dir = download_form.find('input[name=\\'dir\\']');\nconst download_tags = download_form.find('input[name=\\'tags\\']');\nconst download_keywords = download_form.find('input[name=\\'keywords\\']');\n\ndocument.getElementById('add-to-playlist-btn').addEventListener('click', () => {\n  const data = getFilters();\n  data.action = 'add';\n\n  console.log(data);\n\n  $.ajax({\n    type: 'POST',\n    url: 'library',\n    data: data,\n  });\n\n  checkForPlaylistUpdate();\n});\n\ndocument.getElementById('library-delete-btn').addEventListener('click', () => {\n  const data = getFilters();\n  data.action = 'delete';\n\n  console.log(data);\n\n  $.ajax({\n    type: 'POST',\n    url: 'library',\n    data: data,\n  });\n\n  checkForPlaylistUpdate();\n  updateResults();\n});\n\ndocument.getElementById('library-download-btn').addEventListener('click', () => {\n  const cond = getFilters();\n  download_id.val();\n  download_type.val(cond.type);\n  download_dir.val(cond.dir);\n  download_tags.val(cond.tags);\n  download_keywords.val(cond.keywords);\n  download_form.submit();\n});\n\ndocument.getElementById('library-rescan-btn').addEventListener('click', () => {\n  request('post', {action: 'rescan'});\n  updateResults();\n});\n\nfunction downloadId(id) {\n  download_id.attr('value', id);\n  download_type.attr('value', '');\n  download_dir.attr('value', '');\n  download_tags.attr('value', '');\n  download_keywords.attr('value', '');\n  download_form.submit();\n}\n\nconst page_ul = $('#library-page-ul');\nconst page_li = $('.library-page-li');\nconst page_no = $('.library-page-no');\n\nfunction processResults(data) {\n  lib_group.animate({\n    opacity: 0,\n  }, 200, function() {\n    lib_loading.hide();\n    const total_pages = data.total_pages;\n    const active_page = data.active_page;\n    const items = data.items;\n    if (items.length === 0) {\n      lib_loading.hide();\n      lib_empty.show();\n      page_ul.empty();\n      lib_group.animate({opacity: 1}, 200);\n      return;\n    }\n    items.forEach(\n        function(item) {\n          addResultItem(item);\n          bindLibraryResultEvent();\n        },\n    );\n\n    page_ul.empty();\n    page_li.removeClass('active').empty();\n\n    let i = 1;\n    let page_li_copy;\n    let page_no_copy;\n\n    if (total_pages > 25) {\n      i = (active_page - 12 >= 1) ? active_page - 12 : 1;\n      const _i = total_pages - 23;\n      i = (i < _i) ? i : _i;\n      page_li_copy = page_li.clone();\n      page_no_copy = page_no.clone();\n      page_no_copy.html('&laquo;');\n\n      page_no_copy.click(function(e) {\n        updateResults(1);\n      });\n\n      page_no_copy.appendTo(page_li_copy);\n      page_li_copy.appendTo(page_ul);\n    }\n\n    const limit = i + 24;\n    for (; i <= total_pages && i <= limit; i++) {\n      page_li_copy = page_li.clone();\n      page_no_copy = page_no.clone();\n      page_no_copy.html(i.toString());\n      if (active_page === i) {\n        page_li_copy.addClass('active');\n      } else {\n        page_no_copy.click(function(e) {\n          const _page_no = $(e.currentTarget).html();\n          updateResults(_page_no);\n        });\n      }\n      page_no_copy.appendTo(page_li_copy);\n      page_li_copy.appendTo(page_ul);\n    }\n\n    if (limit < total_pages) {\n      page_li_copy = page_li.clone();\n      page_no_copy = page_no.clone();\n      page_no_copy.html('&raquo;');\n\n      page_no_copy.click(function(e) {\n        updateResults(total_pages);\n      });\n\n      page_no_copy.appendTo(page_li_copy);\n      page_li_copy.appendTo(page_ul);\n    }\n    lib_group.animate({opacity: 1}, 200);\n  });\n}\n\n// ---------------------\n// ------ Tagging ------\n// ---------------------\n\nconst add_tag_modal_title = $('#addTagModalTitle');\nconst add_tag_modal_item_id = $('#addTagModalItemId');\nconst add_tag_modal_tags = $('#addTagModalTags');\nconst add_tag_modal_input = $('#addTagModalInput');\nconst modal_tag = $('.modal-tag');\nconst modal_tag_text = $('.modal-tag-text');\n\nfunction addTagModalShow(_id, _title, _tag_tuples) {\n  add_tag_modal_title.html(_title);\n  add_tag_modal_item_id.val(_id);\n  add_tag_modal_tags.empty();\n  _tag_tuples.forEach(function(tag_tuple) {\n    modal_tag_text.html(tag_tuple[0]);\n    const tag_copy = modal_tag.clone();\n    const modal_tag_remove = tag_copy.find('.modal-tag-remove');\n    modal_tag_remove.click(function(e) {\n      $(e.currentTarget).parent().remove();\n    });\n    tag_copy.show();\n    tag_copy.appendTo(add_tag_modal_tags);\n    modal_tag_text.html('');\n  });\n  addTagModal.show();\n}\n\ndocument.getElementById('addTagModalAddBtn').addEventListener('click', () => {\n  const new_tags = add_tag_modal_input.val().split(',').map(function(str) {\n    return str.trim();\n  });\n  new_tags.forEach(function(tag) {\n    modal_tag_text.html(tag);\n    const tag_copy = modal_tag.clone();\n    const modal_tag_remove = tag_copy.find('.modal-tag-remove');\n    modal_tag_remove.click(function(e) {\n      $(e.currentTarget).parent().remove();\n    });\n    tag_copy.show();\n    tag_copy.appendTo(add_tag_modal_tags);\n    modal_tag_text.html('');\n  });\n  add_tag_modal_input.val('');\n});\n\ndocument.getElementById('addTagModalSubmit').addEventListener('click', () => {\n  const all_tags = $('.modal-tag-text');\n  const tags = [];\n  all_tags.each(function(i, element) {\n    if (element.innerHTML) {\n      tags.push(element.innerHTML);\n    }\n  });\n\n  $.ajax({\n    type: 'POST',\n    url: 'library',\n    data: {\n      action: 'edit_tags',\n      id: add_tag_modal_item_id.val(),\n      tags: tags.join(','),\n    },\n    complete: function() {\n      updateResults(active_page);\n    },\n  });\n});\n\n// ---------------------\n// ------- Volume ------\n// ---------------------\n\nconst volumePopoverBtn = document.getElementById('volume-popover-btn');\nconst volumePopoverDiv = document.getElementById('volume-popover');\nlet volume_popover_instance = null;\nlet volume_popover_show = false;\nlet volume_update_timer;\n\nvolumePopoverBtn.addEventListener('click', function(e) {\n  e.stopPropagation();\n\n  if (!volume_popover_show) {\n    volume_popover_instance = new Popper(volumePopoverBtn, volumePopoverDiv, {\n      placement: 'top',\n      modifiers: {\n        offset: {\n          offset: '0, 8',\n        },\n      },\n    });\n    volumePopoverDiv.setAttribute('data-show', '');\n  } else {\n    volumePopoverDiv.removeAttribute('data-show');\n    if (volume_popover_instance) {\n      volume_popover_instance.destroy();\n      volume_popover_instance = null;\n    }\n  }\n  volume_popover_show = !volume_popover_show;\n\n  document.addEventListener('click', function() {\n    volumePopoverDiv.removeAttribute('data-show');\n    if (volume_popover_instance) {\n      volume_popover_instance.destroy();\n      volume_popover_instance = null;\n      volume_popover_show = !volume_popover_show;\n    }\n  }, {\n    once: true,\n  });\n});\n\nvolumePopoverBtn.addEventListener('click', function(e) {\n  e.stopPropagation();\n});\n\nvolumeSlider.addEventListener('change', (e) => {\n  window.clearTimeout(volume_update_timer);\n\n  volume_update_timer = window.setTimeout(() => {\n    request('post', {\n      action: 'volume_set_value',\n      new_volume: volumeSlider.value,\n    });\n  }, 500); // delay in milliseconds\n});\n\ndocument.getElementById('volume-down-btn').addEventListener('click', () => {\n  request('post', {action: 'volume_down'});\n});\n\ndocument.getElementById('volume-up-btn').addEventListener('click', () => {\n  request('post', {action: 'volume_up'});\n});\n\n// ---------------------\n// ------- Upload ------\n// ---------------------\n\nconst uploadModal = new Modal(document.getElementById('uploadModal'));\n\nconst uploadFileInput = document.getElementById('uploadSelectFile');\nconst uploadModalItem = document.getElementsByClassName('uploadItem')[0];\nconst uploadModalList = document.getElementById('uploadModalList');\nconst uploadTargetDir = document.getElementById('uploadTargetDir');\nconst uploadSuccessAlert = document.getElementById('uploadSuccessAlert');\nconst uploadSubmitBtn = document.getElementById('uploadSubmit');\nconst uploadCancelBtn = document.getElementById('uploadCancel');\nconst uploadCancelTooltip = new Tooltip(uploadCancelBtn);\nconst uploadCloseBtn = document.getElementById('uploadClose');\n\nconst maxFileSize = parseInt(document.getElementById('maxUploadFileSize').value);\n\nlet filesToProceed = [];\nconst filesProgressItem = {};\nlet runningXHR = null;\n\nlet areYouSureToCancelUploading = false;\n\nuploadSubmitBtn.addEventListener('click', uploadStart);\nuploadCancelBtn.addEventListener('click', uploadCancel);\n\nfunction uploadStart() {\n  uploadModalList.textContent = '';\n  uploadSuccessAlert.style.display = 'none';\n  uploadCancelBtn.style.display = 'none';\n  uploadCloseBtn.style.display = 'block';\n  areYouSureToCancelUploading = false;\n  uploadCancelTooltip.hide();\n  const file_list = uploadFileInput.files;\n\n  if (file_list.length) {\n    for (const file of file_list) {\n      generateUploadProgressItem(file);\n      if (file.size > maxFileSize) {\n        setUploadError(file.name, 'File too large!');\n        continue;\n      } else if (!(file.type.includes('audio') || file.type.includes('video'))) {\n        setUploadError(file.name, 'Unsupported media format!');\n        continue;\n      }\n\n      filesToProceed.push(file);\n    }\n\n    uploadFileInput.value = '';\n    uploadModal.show();\n    uploadNextFile();\n  }\n}\n\nfunction setUploadError(filename, error) {\n  const file_progress_item = filesProgressItem[filename];\n\n  file_progress_item.title.classList.add('text-muted');\n  file_progress_item.error.innerHTML += 'Error: ' + error;\n  setProgressBar(file_progress_item.progress, 1);\n  file_progress_item.progress.classList.add('bg-danger');\n  file_progress_item.progress.classList.remove('progress-bar-animated');\n}\n\nfunction generateUploadProgressItem(file) {\n  const item_clone = uploadModalItem.cloneNode(true);\n  const title = item_clone.querySelector('.uploadItemTitle');\n  title.innerHTML = file.name;\n  const error = item_clone.querySelector('.uploadItemError');\n  const progress = item_clone.querySelector('.uploadProgress');\n  item_clone.style.display = 'block';\n\n  const item = {\n    title: title,\n    error: error,\n    progress: progress,\n  };\n  filesProgressItem[file.name] = item;\n  uploadModalList.appendChild(item_clone);\n\n  return item;\n}\n\nfunction uploadNextFile() {\n  uploadCancelBtn.style.display = 'block';\n  uploadCloseBtn.style.display = 'none';\n\n  const req = new XMLHttpRequest();\n  const file = filesToProceed.shift();\n  const file_progress_item = filesProgressItem[file.name];\n\n  req.addEventListener('load', function() {\n    if (this.status === 200) {\n      setProgressBar(file_progress_item.progress, 1);\n      file_progress_item.progress.classList.add('bg-success');\n      file_progress_item.progress.classList.remove('progress-bar-animated');\n    } else if (this.status === 400 || this.status === 403) {\n      setUploadError(file.name, 'Illegal request!');\n    } else if (this.status === 500) {\n      setUploadError(file.name, 'Server internal error!');\n    } else {\n      if (this.responseText) {\n        setUploadError(file.name, this.responseText);\n      } else {\n        setUploadError(file.name, 'Unknown error!');\n      }\n    }\n\n    if (filesToProceed.length) {\n      uploadNextFile();\n    } else {\n      uploadSuccessAlert.style.display = 'block';\n      runningXHR = null;\n\n      uploadCancelBtn.style.display = 'none';\n      uploadCloseBtn.style.display = 'block';\n\n      request('post', {\n        action: 'rescan',\n      });\n      updateResults();\n    }\n  });\n\n  req.upload.addEventListener('progress', function(e) {\n    if (e.lengthComputable) {\n      const percent = e.loaded / e.total;\n      setProgressBar(file_progress_item.progress, percent, Math.floor(percent * 100) + '%');\n    }\n  });\n\n  const form = new FormData();\n  form.append('file', file);\n  form.append('targetdir', uploadTargetDir.value);\n\n  req.open('POST', 'upload');\n  req.withCredentials = true;\n  req.send(form);\n\n  file_progress_item.progress.classList.add('progress-bar-striped');\n  file_progress_item.progress.classList.add('progress-bar-animated');\n\n  runningXHR = req;\n}\n\nfunction uploadCancel() {\n  if (!areYouSureToCancelUploading) {\n    uploadCancelTooltip.show();\n  } else {\n    uploadCancelTooltip.hide();\n    uploadModal.hide();\n    runningXHR.abort();\n    filesToProceed = [];\n    uploadFileInput.value = '';\n    request('post', {\n      action: 'rescan',\n    });\n    updateResults();\n  }\n\n  areYouSureToCancelUploading = !areYouSureToCancelUploading;\n}\n\n//\n// URLS & Radio\n//\n\nconst musicUrlInput = document.getElementById('music-url-input');\nconst radioUrlInput = document.getElementById('radio-url-input');\n\ndocument.getElementById('add-music-url').querySelector('button').addEventListener('click', () => {\n  request('post', {add_url: musicUrlInput.value});\n  musicUrlInput.value = '';\n});\n\ndocument.getElementById('add-radio-url').querySelector('button').addEventListener('click', () => {\n  request('post', {add_radio: radioUrlInput.value});\n  radioUrlInput.value = '';\n});\n\n// ---------------------\n// ------  Player ------\n// ---------------------\n\nconst player = new Toast(document.getElementById('playerToast'));\nconst playerArtwork = document.getElementById('playerArtwork');\nconst playerArtworkIdle = document.getElementById('playerArtworkIdle');\nconst playerTitle = document.getElementById('playerTitle');\nconst playerArtist = document.getElementById('playerArtist');\nconst playerBar = document.getElementById('playerBar');\nconst playerBarBox = document.getElementById('playerBarBox');\nconst playerPlayBtn = document.getElementById('playerPlayBtn');\nconst playerPauseBtn = document.getElementById('playerPauseBtn');\nconst playerSkipBtn = document.getElementById('playerSkipBtn');\n\nlet currentPlayingItem = null;\n\nplayerPlayBtn.addEventListener('click', () => {\n  request('post', {action: 'resume'});\n});\n\nplayerPauseBtn.addEventListener('click', () => {\n  request('post', {action: 'pause'});\n});\n\nplayerSkipBtn.addEventListener('click', () => {\n  request('post', {action: 'next'});\n});\n\ndocument.getElementById('player-toast').addEventListener('click', () => {\n  player.show();\n});\n\nfunction playerSetIdle() {\n  playerArtwork.style.display = 'none';\n  playerArtworkIdle.style.display = 'block';\n  playerTitle.textContent = '-- IDLE --';\n  playerArtist.textContent = '';\n  setProgressBar(playerBar, 0);\n  clearInterval(playhead_timer);\n}\n\nfunction updatePlayerInfo(item) {\n  if (!item) {\n    playerSetIdle();\n    return;\n  }\n  playerArtwork.style.display = 'block';\n  playerArtworkIdle.style.display = 'none';\n  currentPlayingItem = item;\n  playerTitle.textContent = item.title;\n  playerArtist.textContent = item.artist;\n  playerArtwork.setAttribute('src', item.thumbnail);\n  playerArtwork.setAttribute('alt', limitChars(item.title));\n\n  if (isOverflown(playerTitle)) {\n    playerTitle.classList.add('scrolling');\n  } else {\n    playerTitle.classList.remove('scrolling');\n  }\n\n  if (isOverflown(playerArtist)) {\n    playerArtist.classList.add('scrolling');\n  } else {\n    playerArtist.classList.remove('scrolling');\n  }\n}\n\nfunction updatePlayerControls(play, empty) {\n  if (empty) {\n    playerSetIdle();\n    playerPlayBtn.setAttribute('disabled', '');\n    playerPauseBtn.setAttribute('disabled', '');\n    playerSkipBtn.setAttribute('disabled', '');\n  } else {\n    playerPlayBtn.removeAttribute('disabled');\n    playerPauseBtn.removeAttribute('disabled');\n    playerSkipBtn.removeAttribute('disabled');\n  }\n  if (play) {\n    playerPlayBtn.style.display = 'none';\n    playerPauseBtn.style.display = 'block';\n  } else {\n    playerPlayBtn.style.display = 'block';\n    playerPauseBtn.style.display = 'none';\n  }\n}\n\nlet playhead_timer;\nlet player_playhead_position;\nlet playhead_dragging = false;\n\nfunction updatePlayerPlayhead(playhead) {\n  if (!currentPlayingItem || playhead_dragging) {\n    return;\n  }\n  if (currentPlayingItem.duration !== 0 || currentPlayingItem.duration < playhead) {\n    playerBar.classList.remove('progress-bar-animated');\n    clearInterval(playhead_timer);\n    player_playhead_position = playhead;\n    setProgressBar(playerBar, player_playhead_position / currentPlayingItem.duration, secondsToStr(player_playhead_position));\n    if (playing) {\n      playhead_timer = setInterval(function() {\n        player_playhead_position += 0.3;\n        setProgressBar(playerBar, player_playhead_position / currentPlayingItem.duration, secondsToStr(player_playhead_position));\n      }, 300); // delay in milliseconds\n    }\n  } else {\n    if (playing) {\n      playerBar.classList.add('progress-bar-animated');\n    } else {\n      playerBar.classList.remove('progress-bar-animated');\n    }\n    setProgressBar(playerBar, 1);\n  }\n}\n\nplayerBarBox.addEventListener('mousedown', function() {\n  if (currentPlayingItem && currentPlayingItem.duration > 0) {\n    playerBarBox.addEventListener('mousemove', playheadDragged);\n    clearInterval(playhead_timer);\n    playhead_dragging = true;\n  }\n});\n\nplayerBarBox.addEventListener('mouseup', function(event) {\n  playerBarBox.removeEventListener('mousemove', playheadDragged);\n  const percent = (event.clientX - playerBarBox.getBoundingClientRect().x) / playerBarBox.clientWidth;\n  request('post', {\n    move_playhead: percent * currentPlayingItem.duration,\n  });\n  playhead_dragging = false;\n});\n\nfunction playheadDragged(event) {\n  const percent = (event.clientX - playerBarBox.getBoundingClientRect().x) / playerBarBox.clientWidth;\n  setProgressBar(playerBar, percent, secondsToStr(percent * currentPlayingItem.duration));\n}\n\n// -----------------------\n// ----- Application -----\n// -----------------------\n\ndocument.addEventListener('DOMContentLoaded', () => {\n  updateResults();\n  updatePlaylist();\n  updateLibraryControls();\n\n  // Check the version of playlist to see if update is needed.\n  setInterval(checkForPlaylistUpdate, 3000);\n});\n"
  },
  {
    "path": "web/package-lock.json",
    "content": "{\n  \"name\": \"botamusique\",\n  \"lockfileVersion\": 2,\n  \"requires\": true,\n  \"packages\": {\n    \"\": {\n      \"name\": \"botamusique\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"@fortawesome/fontawesome-svg-core\": \"^1.2.32\",\n        \"@fortawesome/free-regular-svg-icons\": \"^5.15.1\",\n        \"@fortawesome/free-solid-svg-icons\": \"^5.15.1\",\n        \"bootstrap\": \"^4.5.3\",\n        \"bootswatch\": \"^4.5.3\",\n        \"jquery\": \"^3.5.1\",\n        \"jquery-migrate\": \"^3.3.2\",\n        \"popper.js\": \"^1.16.1\"\n      },\n      \"devDependencies\": {\n        \"@babel/core\": \"^7.12.9\",\n        \"@babel/eslint-parser\": \"^7.12.1\",\n        \"@babel/eslint-plugin\": \"^7.12.1\",\n        \"@babel/plugin-proposal-class-properties\": \"^7.12.1\",\n        \"@babel/preset-env\": \"^7.12.7\",\n        \"autoprefixer\": \"^10.0.2\",\n        \"babel-loader\": \"^8.2.1\",\n        \"core-js\": \"^3.7.0\",\n        \"css-loader\": \"^5.0.1\",\n        \"eslint\": \"^7.14.0\",\n        \"eslint-plugin-import\": \"^2.22.1\",\n        \"eslint-plugin-jquery\": \"^1.5.1\",\n        \"eslint-plugin-jsdoc\": \"^30.7.8\",\n        \"html-webpack-plugin\": \"^4.5.0\",\n        \"mini-css-extract-plugin\": \"^1.3.1\",\n        \"postcss-loader\": \"^7.2.4\",\n        \"regenerator-runtime\": \"^0.13.7\",\n        \"sass\": \"^1.29.0\",\n        \"sass-loader\": \"^10.1.0\",\n        \"webpack\": \"^5.6.0\",\n        \"webpack-cli\": \"^4.2.0\"\n      }\n    },\n    \"node_modules/@babel/code-frame\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz\",\n      \"integrity\": \"sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/highlight\": \"^7.10.4\"\n      }\n    },\n    \"node_modules/@babel/compat-data\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.7.tgz\",\n      \"integrity\": \"sha512-YaxPMGs/XIWtYqrdEOZOCPsVWfEoriXopnsz3/i7apYPXQ3698UFhS6dVT1KN5qOsWmVgw/FOrmQgpRaZayGsw==\",\n      \"dev\": true\n    },\n    \"node_modules/@babel/core\": {\n      \"version\": \"7.12.9\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz\",\n      \"integrity\": \"sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/code-frame\": \"^7.10.4\",\n        \"@babel/generator\": \"^7.12.5\",\n        \"@babel/helper-module-transforms\": \"^7.12.1\",\n        \"@babel/helpers\": \"^7.12.5\",\n        \"@babel/parser\": \"^7.12.7\",\n        \"@babel/template\": \"^7.12.7\",\n        \"@babel/traverse\": \"^7.12.9\",\n        \"@babel/types\": \"^7.12.7\",\n        \"convert-source-map\": \"^1.7.0\",\n        \"debug\": \"^4.1.0\",\n        \"gensync\": \"^1.0.0-beta.1\",\n        \"json5\": \"^2.1.2\",\n        \"lodash\": \"^4.17.19\",\n        \"resolve\": \"^1.3.2\",\n        \"semver\": \"^5.4.1\",\n        \"source-map\": \"^0.5.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6.9.0\"\n      },\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/babel\"\n      }\n    },\n    \"node_modules/@babel/eslint-parser\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.12.1.tgz\",\n      \"integrity\": \"sha512-cc7WQHnHQY3++/bghgbDtPx+5bf6xTsokyGzV6Qzh65NLz/unv+mPQuACkQ9GFhIhcTFv6yqwNaEcfX7EkOEsg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"eslint-scope\": \"5.1.0\",\n        \"eslint-visitor-keys\": \"^1.3.0\",\n        \"semver\": \"^6.3.0\"\n      },\n      \"engines\": {\n        \"node\": \"^10.13.0 || ^12.13.0 || >=14.0.0\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \">=7.11.0\",\n        \"eslint\": \">=7.5.0\"\n      }\n    },\n    \"node_modules/@babel/eslint-parser/node_modules/semver\": {\n      \"version\": \"6.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/semver/-/semver-6.3.0.tgz\",\n      \"integrity\": \"sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==\",\n      \"dev\": true,\n      \"bin\": {\n        \"semver\": \"bin/semver.js\"\n      }\n    },\n    \"node_modules/@babel/eslint-plugin\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/eslint-plugin/-/eslint-plugin-7.12.1.tgz\",\n      \"integrity\": \"sha512-rOjrD5yupTYCO4x0kEbQmi/NsaD+VGOD/9Cvso64WMVPY2y6o5Nvw2sqFWdeSEBdR1Dsa07YjplBs067x5YbXg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"eslint-rule-composer\": \"^0.3.0\"\n      },\n      \"engines\": {\n        \"node\": \"^10.13.0 || ^12.13.0 || >=14.0.0\"\n      },\n      \"peerDependencies\": {\n        \"@babel/eslint-parser\": \">=7.11.0\",\n        \"eslint\": \">=7.5.0\"\n      }\n    },\n    \"node_modules/@babel/generator\": {\n      \"version\": \"7.12.5\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/generator/-/generator-7.12.5.tgz\",\n      \"integrity\": \"sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/types\": \"^7.12.5\",\n        \"jsesc\": \"^2.5.1\",\n        \"source-map\": \"^0.5.0\"\n      }\n    },\n    \"node_modules/@babel/helper-annotate-as-pure\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz\",\n      \"integrity\": \"sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/types\": \"^7.10.4\"\n      }\n    },\n    \"node_modules/@babel/helper-builder-binary-assignment-operator-visitor\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz\",\n      \"integrity\": \"sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-explode-assignable-expression\": \"^7.10.4\",\n        \"@babel/types\": \"^7.10.4\"\n      }\n    },\n    \"node_modules/@babel/helper-compilation-targets\": {\n      \"version\": \"7.12.5\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.5.tgz\",\n      \"integrity\": \"sha512-+qH6NrscMolUlzOYngSBMIOQpKUGPPsc61Bu5W10mg84LxZ7cmvnBHzARKbDoFxVvqqAbj6Tg6N7bSrWSPXMyw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/compat-data\": \"^7.12.5\",\n        \"@babel/helper-validator-option\": \"^7.12.1\",\n        \"browserslist\": \"^4.14.5\",\n        \"semver\": \"^5.5.0\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0\"\n      }\n    },\n    \"node_modules/@babel/helper-create-class-features-plugin\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz\",\n      \"integrity\": \"sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-function-name\": \"^7.10.4\",\n        \"@babel/helper-member-expression-to-functions\": \"^7.12.1\",\n        \"@babel/helper-optimise-call-expression\": \"^7.10.4\",\n        \"@babel/helper-replace-supers\": \"^7.12.1\",\n        \"@babel/helper-split-export-declaration\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0\"\n      }\n    },\n    \"node_modules/@babel/helper-create-regexp-features-plugin\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.7.tgz\",\n      \"integrity\": \"sha512-idnutvQPdpbduutvi3JVfEgcVIHooQnhvhx0Nk9isOINOIGYkZea1Pk2JlJRiUnMefrlvr0vkByATBY/mB4vjQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-annotate-as-pure\": \"^7.10.4\",\n        \"regexpu-core\": \"^4.7.1\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0\"\n      }\n    },\n    \"node_modules/@babel/helper-define-map\": {\n      \"version\": \"7.10.5\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz\",\n      \"integrity\": \"sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-function-name\": \"^7.10.4\",\n        \"@babel/types\": \"^7.10.5\",\n        \"lodash\": \"^4.17.19\"\n      }\n    },\n    \"node_modules/@babel/helper-explode-assignable-expression\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz\",\n      \"integrity\": \"sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/types\": \"^7.12.1\"\n      }\n    },\n    \"node_modules/@babel/helper-function-name\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz\",\n      \"integrity\": \"sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-get-function-arity\": \"^7.10.4\",\n        \"@babel/template\": \"^7.10.4\",\n        \"@babel/types\": \"^7.10.4\"\n      }\n    },\n    \"node_modules/@babel/helper-get-function-arity\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz\",\n      \"integrity\": \"sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/types\": \"^7.10.4\"\n      }\n    },\n    \"node_modules/@babel/helper-hoist-variables\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz\",\n      \"integrity\": \"sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/types\": \"^7.10.4\"\n      }\n    },\n    \"node_modules/@babel/helper-member-expression-to-functions\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz\",\n      \"integrity\": \"sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/types\": \"^7.12.7\"\n      }\n    },\n    \"node_modules/@babel/helper-module-imports\": {\n      \"version\": \"7.12.5\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz\",\n      \"integrity\": \"sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/types\": \"^7.12.5\"\n      }\n    },\n    \"node_modules/@babel/helper-module-transforms\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz\",\n      \"integrity\": \"sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-module-imports\": \"^7.12.1\",\n        \"@babel/helper-replace-supers\": \"^7.12.1\",\n        \"@babel/helper-simple-access\": \"^7.12.1\",\n        \"@babel/helper-split-export-declaration\": \"^7.11.0\",\n        \"@babel/helper-validator-identifier\": \"^7.10.4\",\n        \"@babel/template\": \"^7.10.4\",\n        \"@babel/traverse\": \"^7.12.1\",\n        \"@babel/types\": \"^7.12.1\",\n        \"lodash\": \"^4.17.19\"\n      }\n    },\n    \"node_modules/@babel/helper-optimise-call-expression\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.7.tgz\",\n      \"integrity\": \"sha512-I5xc9oSJ2h59OwyUqjv95HRyzxj53DAubUERgQMrpcCEYQyToeHA+NEcUEsVWB4j53RDeskeBJ0SgRAYHDBckw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/types\": \"^7.12.7\"\n      }\n    },\n    \"node_modules/@babel/helper-plugin-utils\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz\",\n      \"integrity\": \"sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==\",\n      \"dev\": true\n    },\n    \"node_modules/@babel/helper-remap-async-to-generator\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz\",\n      \"integrity\": \"sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-annotate-as-pure\": \"^7.10.4\",\n        \"@babel/helper-wrap-function\": \"^7.10.4\",\n        \"@babel/types\": \"^7.12.1\"\n      }\n    },\n    \"node_modules/@babel/helper-replace-supers\": {\n      \"version\": \"7.12.5\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.5.tgz\",\n      \"integrity\": \"sha512-5YILoed0ZyIpF4gKcpZitEnXEJ9UoDRki1Ey6xz46rxOzfNMAhVIJMoune1hmPVxh40LRv1+oafz7UsWX+vyWA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-member-expression-to-functions\": \"^7.12.1\",\n        \"@babel/helper-optimise-call-expression\": \"^7.10.4\",\n        \"@babel/traverse\": \"^7.12.5\",\n        \"@babel/types\": \"^7.12.5\"\n      }\n    },\n    \"node_modules/@babel/helper-simple-access\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz\",\n      \"integrity\": \"sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/types\": \"^7.12.1\"\n      }\n    },\n    \"node_modules/@babel/helper-skip-transparent-expression-wrappers\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz\",\n      \"integrity\": \"sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/types\": \"^7.12.1\"\n      }\n    },\n    \"node_modules/@babel/helper-split-export-declaration\": {\n      \"version\": \"7.11.0\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz\",\n      \"integrity\": \"sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/types\": \"^7.11.0\"\n      }\n    },\n    \"node_modules/@babel/helper-validator-identifier\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz\",\n      \"integrity\": \"sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==\",\n      \"dev\": true\n    },\n    \"node_modules/@babel/helper-validator-option\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz\",\n      \"integrity\": \"sha512-YpJabsXlJVWP0USHjnC/AQDTLlZERbON577YUVO/wLpqyj6HAtVYnWaQaN0iUN+1/tWn3c+uKKXjRut5115Y2A==\",\n      \"dev\": true\n    },\n    \"node_modules/@babel/helper-wrap-function\": {\n      \"version\": \"7.12.3\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz\",\n      \"integrity\": \"sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-function-name\": \"^7.10.4\",\n        \"@babel/template\": \"^7.10.4\",\n        \"@babel/traverse\": \"^7.10.4\",\n        \"@babel/types\": \"^7.10.4\"\n      }\n    },\n    \"node_modules/@babel/helpers\": {\n      \"version\": \"7.12.5\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz\",\n      \"integrity\": \"sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/template\": \"^7.10.4\",\n        \"@babel/traverse\": \"^7.12.5\",\n        \"@babel/types\": \"^7.12.5\"\n      }\n    },\n    \"node_modules/@babel/highlight\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz\",\n      \"integrity\": \"sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-validator-identifier\": \"^7.10.4\",\n        \"chalk\": \"^2.0.0\",\n        \"js-tokens\": \"^4.0.0\"\n      }\n    },\n    \"node_modules/@babel/parser\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/parser/-/parser-7.12.7.tgz\",\n      \"integrity\": \"sha512-oWR02Ubp4xTLCAqPRiNIuMVgNO5Aif/xpXtabhzW2HWUD47XJsAB4Zd/Rg30+XeQA3juXigV7hlquOTmwqLiwg==\",\n      \"dev\": true,\n      \"bin\": {\n        \"parser\": \"bin/babel-parser.js\"\n      },\n      \"engines\": {\n        \"node\": \">=6.0.0\"\n      }\n    },\n    \"node_modules/@babel/plugin-proposal-async-generator-functions\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz\",\n      \"integrity\": \"sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-remap-async-to-generator\": \"^7.12.1\",\n        \"@babel/plugin-syntax-async-generators\": \"^7.8.0\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-proposal-class-properties\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz\",\n      \"integrity\": \"sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-create-class-features-plugin\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-proposal-dynamic-import\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz\",\n      \"integrity\": \"sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-dynamic-import\": \"^7.8.0\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-proposal-export-namespace-from\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz\",\n      \"integrity\": \"sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-export-namespace-from\": \"^7.8.3\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-proposal-json-strings\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz\",\n      \"integrity\": \"sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-json-strings\": \"^7.8.0\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-proposal-logical-assignment-operators\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz\",\n      \"integrity\": \"sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-logical-assignment-operators\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-proposal-nullish-coalescing-operator\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz\",\n      \"integrity\": \"sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-nullish-coalescing-operator\": \"^7.8.0\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-proposal-numeric-separator\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.7.tgz\",\n      \"integrity\": \"sha512-8c+uy0qmnRTeukiGsjLGy6uVs/TFjJchGXUeBqlG4VWYOdJWkhhVPdQ3uHwbmalfJwv2JsV0qffXP4asRfL2SQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-numeric-separator\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-proposal-object-rest-spread\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz\",\n      \"integrity\": \"sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-object-rest-spread\": \"^7.8.0\",\n        \"@babel/plugin-transform-parameters\": \"^7.12.1\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-proposal-optional-catch-binding\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz\",\n      \"integrity\": \"sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-optional-catch-binding\": \"^7.8.0\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-proposal-optional-chaining\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.7.tgz\",\n      \"integrity\": \"sha512-4ovylXZ0PWmwoOvhU2vhnzVNnm88/Sm9nx7V8BPgMvAzn5zDou3/Awy0EjglyubVHasJj+XCEkr/r1X3P5elCA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-skip-transparent-expression-wrappers\": \"^7.12.1\",\n        \"@babel/plugin-syntax-optional-chaining\": \"^7.8.0\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-proposal-private-methods\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz\",\n      \"integrity\": \"sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-create-class-features-plugin\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-proposal-unicode-property-regex\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz\",\n      \"integrity\": \"sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-create-regexp-features-plugin\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-syntax-async-generators\": {\n      \"version\": \"7.8.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz\",\n      \"integrity\": \"sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-syntax-class-properties\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz\",\n      \"integrity\": \"sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-syntax-dynamic-import\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz\",\n      \"integrity\": \"sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-syntax-export-namespace-from\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz\",\n      \"integrity\": \"sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.3\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-syntax-json-strings\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz\",\n      \"integrity\": \"sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-syntax-logical-assignment-operators\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz\",\n      \"integrity\": \"sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-syntax-nullish-coalescing-operator\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz\",\n      \"integrity\": \"sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-syntax-numeric-separator\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz\",\n      \"integrity\": \"sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-syntax-object-rest-spread\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz\",\n      \"integrity\": \"sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-syntax-optional-catch-binding\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz\",\n      \"integrity\": \"sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-syntax-optional-chaining\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz\",\n      \"integrity\": \"sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-syntax-top-level-await\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz\",\n      \"integrity\": \"sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-arrow-functions\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz\",\n      \"integrity\": \"sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-async-to-generator\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz\",\n      \"integrity\": \"sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-module-imports\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-remap-async-to-generator\": \"^7.12.1\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-block-scoped-functions\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz\",\n      \"integrity\": \"sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-block-scoping\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz\",\n      \"integrity\": \"sha512-zJyAC9sZdE60r1nVQHblcfCj29Dh2Y0DOvlMkcqSo0ckqjiCwNiUezUKw+RjOCwGfpLRwnAeQ2XlLpsnGkvv9w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-classes\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz\",\n      \"integrity\": \"sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-annotate-as-pure\": \"^7.10.4\",\n        \"@babel/helper-define-map\": \"^7.10.4\",\n        \"@babel/helper-function-name\": \"^7.10.4\",\n        \"@babel/helper-optimise-call-expression\": \"^7.10.4\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-replace-supers\": \"^7.12.1\",\n        \"@babel/helper-split-export-declaration\": \"^7.10.4\",\n        \"globals\": \"^11.1.0\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-computed-properties\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz\",\n      \"integrity\": \"sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-destructuring\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz\",\n      \"integrity\": \"sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-dotall-regex\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz\",\n      \"integrity\": \"sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-create-regexp-features-plugin\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-duplicate-keys\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz\",\n      \"integrity\": \"sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-exponentiation-operator\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz\",\n      \"integrity\": \"sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-builder-binary-assignment-operator-visitor\": \"^7.10.4\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-for-of\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz\",\n      \"integrity\": \"sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-function-name\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz\",\n      \"integrity\": \"sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-function-name\": \"^7.10.4\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-literals\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz\",\n      \"integrity\": \"sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-member-expression-literals\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz\",\n      \"integrity\": \"sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-modules-amd\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz\",\n      \"integrity\": \"sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-module-transforms\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"babel-plugin-dynamic-import-node\": \"^2.3.3\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-modules-commonjs\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz\",\n      \"integrity\": \"sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-module-transforms\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-simple-access\": \"^7.12.1\",\n        \"babel-plugin-dynamic-import-node\": \"^2.3.3\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-modules-systemjs\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz\",\n      \"integrity\": \"sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-hoist-variables\": \"^7.10.4\",\n        \"@babel/helper-module-transforms\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-validator-identifier\": \"^7.10.4\",\n        \"babel-plugin-dynamic-import-node\": \"^2.3.3\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-modules-umd\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz\",\n      \"integrity\": \"sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-module-transforms\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-named-capturing-groups-regex\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz\",\n      \"integrity\": \"sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-create-regexp-features-plugin\": \"^7.12.1\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-new-target\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz\",\n      \"integrity\": \"sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-object-super\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz\",\n      \"integrity\": \"sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-replace-supers\": \"^7.12.1\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-parameters\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz\",\n      \"integrity\": \"sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-property-literals\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz\",\n      \"integrity\": \"sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-regenerator\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz\",\n      \"integrity\": \"sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"regenerator-transform\": \"^0.14.2\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-reserved-words\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz\",\n      \"integrity\": \"sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-shorthand-properties\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz\",\n      \"integrity\": \"sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-spread\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz\",\n      \"integrity\": \"sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-skip-transparent-expression-wrappers\": \"^7.12.1\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-sticky-regex\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.7.tgz\",\n      \"integrity\": \"sha512-VEiqZL5N/QvDbdjfYQBhruN0HYjSPjC4XkeqW4ny/jNtH9gcbgaqBIXYEZCNnESMAGs0/K/R7oFGMhOyu/eIxg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-template-literals\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz\",\n      \"integrity\": \"sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-typeof-symbol\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.1.tgz\",\n      \"integrity\": \"sha512-EPGgpGy+O5Kg5pJFNDKuxt9RdmTgj5sgrus2XVeMp/ZIbOESadgILUbm50SNpghOh3/6yrbsH+NB5+WJTmsA7Q==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-unicode-escapes\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz\",\n      \"integrity\": \"sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/plugin-transform-unicode-regex\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz\",\n      \"integrity\": \"sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-create-regexp-features-plugin\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/preset-env\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.7.tgz\",\n      \"integrity\": \"sha512-OnNdfAr1FUQg7ksb7bmbKoby4qFOHw6DKWWUNB9KqnnCldxhxJlP+21dpyaWFmf2h0rTbOkXJtAGevY3XW1eew==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/compat-data\": \"^7.12.7\",\n        \"@babel/helper-compilation-targets\": \"^7.12.5\",\n        \"@babel/helper-module-imports\": \"^7.12.5\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-validator-option\": \"^7.12.1\",\n        \"@babel/plugin-proposal-async-generator-functions\": \"^7.12.1\",\n        \"@babel/plugin-proposal-class-properties\": \"^7.12.1\",\n        \"@babel/plugin-proposal-dynamic-import\": \"^7.12.1\",\n        \"@babel/plugin-proposal-export-namespace-from\": \"^7.12.1\",\n        \"@babel/plugin-proposal-json-strings\": \"^7.12.1\",\n        \"@babel/plugin-proposal-logical-assignment-operators\": \"^7.12.1\",\n        \"@babel/plugin-proposal-nullish-coalescing-operator\": \"^7.12.1\",\n        \"@babel/plugin-proposal-numeric-separator\": \"^7.12.7\",\n        \"@babel/plugin-proposal-object-rest-spread\": \"^7.12.1\",\n        \"@babel/plugin-proposal-optional-catch-binding\": \"^7.12.1\",\n        \"@babel/plugin-proposal-optional-chaining\": \"^7.12.7\",\n        \"@babel/plugin-proposal-private-methods\": \"^7.12.1\",\n        \"@babel/plugin-proposal-unicode-property-regex\": \"^7.12.1\",\n        \"@babel/plugin-syntax-async-generators\": \"^7.8.0\",\n        \"@babel/plugin-syntax-class-properties\": \"^7.12.1\",\n        \"@babel/plugin-syntax-dynamic-import\": \"^7.8.0\",\n        \"@babel/plugin-syntax-export-namespace-from\": \"^7.8.3\",\n        \"@babel/plugin-syntax-json-strings\": \"^7.8.0\",\n        \"@babel/plugin-syntax-logical-assignment-operators\": \"^7.10.4\",\n        \"@babel/plugin-syntax-nullish-coalescing-operator\": \"^7.8.0\",\n        \"@babel/plugin-syntax-numeric-separator\": \"^7.10.4\",\n        \"@babel/plugin-syntax-object-rest-spread\": \"^7.8.0\",\n        \"@babel/plugin-syntax-optional-catch-binding\": \"^7.8.0\",\n        \"@babel/plugin-syntax-optional-chaining\": \"^7.8.0\",\n        \"@babel/plugin-syntax-top-level-await\": \"^7.12.1\",\n        \"@babel/plugin-transform-arrow-functions\": \"^7.12.1\",\n        \"@babel/plugin-transform-async-to-generator\": \"^7.12.1\",\n        \"@babel/plugin-transform-block-scoped-functions\": \"^7.12.1\",\n        \"@babel/plugin-transform-block-scoping\": \"^7.12.1\",\n        \"@babel/plugin-transform-classes\": \"^7.12.1\",\n        \"@babel/plugin-transform-computed-properties\": \"^7.12.1\",\n        \"@babel/plugin-transform-destructuring\": \"^7.12.1\",\n        \"@babel/plugin-transform-dotall-regex\": \"^7.12.1\",\n        \"@babel/plugin-transform-duplicate-keys\": \"^7.12.1\",\n        \"@babel/plugin-transform-exponentiation-operator\": \"^7.12.1\",\n        \"@babel/plugin-transform-for-of\": \"^7.12.1\",\n        \"@babel/plugin-transform-function-name\": \"^7.12.1\",\n        \"@babel/plugin-transform-literals\": \"^7.12.1\",\n        \"@babel/plugin-transform-member-expression-literals\": \"^7.12.1\",\n        \"@babel/plugin-transform-modules-amd\": \"^7.12.1\",\n        \"@babel/plugin-transform-modules-commonjs\": \"^7.12.1\",\n        \"@babel/plugin-transform-modules-systemjs\": \"^7.12.1\",\n        \"@babel/plugin-transform-modules-umd\": \"^7.12.1\",\n        \"@babel/plugin-transform-named-capturing-groups-regex\": \"^7.12.1\",\n        \"@babel/plugin-transform-new-target\": \"^7.12.1\",\n        \"@babel/plugin-transform-object-super\": \"^7.12.1\",\n        \"@babel/plugin-transform-parameters\": \"^7.12.1\",\n        \"@babel/plugin-transform-property-literals\": \"^7.12.1\",\n        \"@babel/plugin-transform-regenerator\": \"^7.12.1\",\n        \"@babel/plugin-transform-reserved-words\": \"^7.12.1\",\n        \"@babel/plugin-transform-shorthand-properties\": \"^7.12.1\",\n        \"@babel/plugin-transform-spread\": \"^7.12.1\",\n        \"@babel/plugin-transform-sticky-regex\": \"^7.12.7\",\n        \"@babel/plugin-transform-template-literals\": \"^7.12.1\",\n        \"@babel/plugin-transform-typeof-symbol\": \"^7.12.1\",\n        \"@babel/plugin-transform-unicode-escapes\": \"^7.12.1\",\n        \"@babel/plugin-transform-unicode-regex\": \"^7.12.1\",\n        \"@babel/preset-modules\": \"^0.1.3\",\n        \"@babel/types\": \"^7.12.7\",\n        \"core-js-compat\": \"^3.7.0\",\n        \"semver\": \"^5.5.0\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/preset-modules\": {\n      \"version\": \"0.1.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz\",\n      \"integrity\": \"sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-plugin-utils\": \"^7.0.0\",\n        \"@babel/plugin-proposal-unicode-property-regex\": \"^7.4.4\",\n        \"@babel/plugin-transform-dotall-regex\": \"^7.4.4\",\n        \"@babel/types\": \"^7.4.4\",\n        \"esutils\": \"^2.0.2\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0-0\"\n      }\n    },\n    \"node_modules/@babel/runtime\": {\n      \"version\": \"7.12.5\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz\",\n      \"integrity\": \"sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"regenerator-runtime\": \"^0.13.4\"\n      }\n    },\n    \"node_modules/@babel/template\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz\",\n      \"integrity\": \"sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/code-frame\": \"^7.10.4\",\n        \"@babel/parser\": \"^7.12.7\",\n        \"@babel/types\": \"^7.12.7\"\n      }\n    },\n    \"node_modules/@babel/traverse\": {\n      \"version\": \"7.12.9\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.9.tgz\",\n      \"integrity\": \"sha512-iX9ajqnLdoU1s1nHt36JDI9KG4k+vmI8WgjK5d+aDTwQbL2fUnzedNedssA645Ede3PM2ma1n8Q4h2ohwXgMXw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/code-frame\": \"^7.10.4\",\n        \"@babel/generator\": \"^7.12.5\",\n        \"@babel/helper-function-name\": \"^7.10.4\",\n        \"@babel/helper-split-export-declaration\": \"^7.11.0\",\n        \"@babel/parser\": \"^7.12.7\",\n        \"@babel/types\": \"^7.12.7\",\n        \"debug\": \"^4.1.0\",\n        \"globals\": \"^11.1.0\",\n        \"lodash\": \"^4.17.19\"\n      }\n    },\n    \"node_modules/@babel/types\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/types/-/types-7.12.7.tgz\",\n      \"integrity\": \"sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/helper-validator-identifier\": \"^7.10.4\",\n        \"lodash\": \"^4.17.19\",\n        \"to-fast-properties\": \"^2.0.0\"\n      }\n    },\n    \"node_modules/@cspotcode/source-map-support\": {\n      \"version\": \"0.8.1\",\n      \"resolved\": \"https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz\",\n      \"integrity\": \"sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==\",\n      \"dev\": true,\n      \"peer\": true,\n      \"dependencies\": {\n        \"@jridgewell/trace-mapping\": \"0.3.9\"\n      },\n      \"engines\": {\n        \"node\": \">=12\"\n      }\n    },\n    \"node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping\": {\n      \"version\": \"0.3.9\",\n      \"resolved\": \"https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz\",\n      \"integrity\": \"sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==\",\n      \"dev\": true,\n      \"peer\": true,\n      \"dependencies\": {\n        \"@jridgewell/resolve-uri\": \"^3.0.3\",\n        \"@jridgewell/sourcemap-codec\": \"^1.4.10\"\n      }\n    },\n    \"node_modules/@eslint/eslintrc\": {\n      \"version\": \"0.2.1\",\n      \"resolved\": \"https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.1.tgz\",\n      \"integrity\": \"sha512-XRUeBZ5zBWLYgSANMpThFddrZZkEbGHgUdt5UJjZfnlN9BGCiUBrf+nvbRupSjMvqzwnQN0qwCmOxITt1cfywA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"ajv\": \"^6.12.4\",\n        \"debug\": \"^4.1.1\",\n        \"espree\": \"^7.3.0\",\n        \"globals\": \"^12.1.0\",\n        \"ignore\": \"^4.0.6\",\n        \"import-fresh\": \"^3.2.1\",\n        \"js-yaml\": \"^3.13.1\",\n        \"lodash\": \"^4.17.19\",\n        \"minimatch\": \"^3.0.4\",\n        \"strip-json-comments\": \"^3.1.1\"\n      },\n      \"engines\": {\n        \"node\": \"^10.12.0 || >=12.0.0\"\n      }\n    },\n    \"node_modules/@eslint/eslintrc/node_modules/globals\": {\n      \"version\": \"12.4.0\",\n      \"resolved\": \"https://registry.npmjs.org/globals/-/globals-12.4.0.tgz\",\n      \"integrity\": \"sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"type-fest\": \"^0.8.1\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/sindresorhus\"\n      }\n    },\n    \"node_modules/@fortawesome/fontawesome-common-types\": {\n      \"version\": \"0.2.32\",\n      \"resolved\": \"https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.32.tgz\",\n      \"integrity\": \"sha512-ux2EDjKMpcdHBVLi/eWZynnPxs0BtFVXJkgHIxXRl+9ZFaHPvYamAfCzeeQFqHRjuJtX90wVnMRaMQAAlctz3w==\",\n      \"hasInstallScript\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/@fortawesome/fontawesome-svg-core\": {\n      \"version\": \"1.2.32\",\n      \"resolved\": \"https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.32.tgz\",\n      \"integrity\": \"sha512-XjqyeLCsR/c/usUpdWcOdVtWFVjPbDFBTQkn2fQRrWhhUoxriQohO2RWDxLyUM8XpD+Zzg5xwJ8gqTYGDLeGaQ==\",\n      \"hasInstallScript\": true,\n      \"dependencies\": {\n        \"@fortawesome/fontawesome-common-types\": \"^0.2.32\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/@fortawesome/free-regular-svg-icons\": {\n      \"version\": \"5.15.1\",\n      \"resolved\": \"https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.1.tgz\",\n      \"integrity\": \"sha512-eD9NWFy89e7SVVtrLedJUxIpCBGhd4x7s7dhesokjyo1Tw62daqN5UcuAGu1NrepLLq1IeAYUVfWwnOjZ/j3HA==\",\n      \"hasInstallScript\": true,\n      \"dependencies\": {\n        \"@fortawesome/fontawesome-common-types\": \"^0.2.32\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/@fortawesome/free-solid-svg-icons\": {\n      \"version\": \"5.15.1\",\n      \"resolved\": \"https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.1.tgz\",\n      \"integrity\": \"sha512-EFMuKtzRMNbvjab/SvJBaOOpaqJfdSap/Nl6hst7CgrJxwfORR1drdTV6q1Ib/JVzq4xObdTDcT6sqTaXMqfdg==\",\n      \"hasInstallScript\": true,\n      \"dependencies\": {\n        \"@fortawesome/fontawesome-common-types\": \"^0.2.32\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/@jridgewell/gen-mapping\": {\n      \"version\": \"0.3.2\",\n      \"resolved\": \"https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz\",\n      \"integrity\": \"sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@jridgewell/set-array\": \"^1.0.1\",\n        \"@jridgewell/sourcemap-codec\": \"^1.4.10\",\n        \"@jridgewell/trace-mapping\": \"^0.3.9\"\n      },\n      \"engines\": {\n        \"node\": \">=6.0.0\"\n      }\n    },\n    \"node_modules/@jridgewell/resolve-uri\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz\",\n      \"integrity\": \"sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6.0.0\"\n      }\n    },\n    \"node_modules/@jridgewell/set-array\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz\",\n      \"integrity\": \"sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6.0.0\"\n      }\n    },\n    \"node_modules/@jridgewell/source-map\": {\n      \"version\": \"0.3.2\",\n      \"resolved\": \"https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz\",\n      \"integrity\": \"sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@jridgewell/gen-mapping\": \"^0.3.0\",\n        \"@jridgewell/trace-mapping\": \"^0.3.9\"\n      }\n    },\n    \"node_modules/@jridgewell/sourcemap-codec\": {\n      \"version\": \"1.4.14\",\n      \"resolved\": \"https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz\",\n      \"integrity\": \"sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==\",\n      \"dev\": true\n    },\n    \"node_modules/@jridgewell/trace-mapping\": {\n      \"version\": \"0.3.14\",\n      \"resolved\": \"https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz\",\n      \"integrity\": \"sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@jridgewell/resolve-uri\": \"^3.0.3\",\n        \"@jridgewell/sourcemap-codec\": \"^1.4.10\"\n      }\n    },\n    \"node_modules/@tsconfig/node10\": {\n      \"version\": \"1.0.9\",\n      \"resolved\": \"https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz\",\n      \"integrity\": \"sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"node_modules/@tsconfig/node12\": {\n      \"version\": \"1.0.11\",\n      \"resolved\": \"https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz\",\n      \"integrity\": \"sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"node_modules/@tsconfig/node14\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz\",\n      \"integrity\": \"sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"node_modules/@tsconfig/node16\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz\",\n      \"integrity\": \"sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"node_modules/@types/anymatch\": {\n      \"version\": \"1.3.1\",\n      \"resolved\": \"https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz\",\n      \"integrity\": \"sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==\",\n      \"dev\": true\n    },\n    \"node_modules/@types/eslint\": {\n      \"version\": \"7.2.5\",\n      \"resolved\": \"https://registry.npmjs.org/@types/eslint/-/eslint-7.2.5.tgz\",\n      \"integrity\": \"sha512-Dc6ar9x16BdaR3NSxSF7T4IjL9gxxViJq8RmFd+2UAyA+K6ck2W+gUwfgpG/y9TPyUuBL35109bbULpEynvltA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@types/estree\": \"*\",\n        \"@types/json-schema\": \"*\"\n      }\n    },\n    \"node_modules/@types/eslint-scope\": {\n      \"version\": \"3.7.0\",\n      \"resolved\": \"https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.0.tgz\",\n      \"integrity\": \"sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@types/eslint\": \"*\",\n        \"@types/estree\": \"*\"\n      }\n    },\n    \"node_modules/@types/estree\": {\n      \"version\": \"0.0.45\",\n      \"resolved\": \"https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz\",\n      \"integrity\": \"sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==\",\n      \"dev\": true\n    },\n    \"node_modules/@types/html-minifier-terser\": {\n      \"version\": \"5.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz\",\n      \"integrity\": \"sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==\",\n      \"dev\": true\n    },\n    \"node_modules/@types/json-schema\": {\n      \"version\": \"7.0.6\",\n      \"resolved\": \"https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz\",\n      \"integrity\": \"sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==\",\n      \"dev\": true\n    },\n    \"node_modules/@types/json5\": {\n      \"version\": \"0.0.29\",\n      \"resolved\": \"https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz\",\n      \"integrity\": \"sha1-7ihweulOEdK4J7y+UnC86n8+ce4=\",\n      \"dev\": true\n    },\n    \"node_modules/@types/node\": {\n      \"version\": \"14.14.9\",\n      \"resolved\": \"https://registry.npmjs.org/@types/node/-/node-14.14.9.tgz\",\n      \"integrity\": \"sha512-JsoLXFppG62tWTklIoO4knA+oDTYsmqWxHRvd4lpmfQRNhX6osheUOWETP2jMoV/2bEHuMra8Pp3Dmo/stBFcw==\",\n      \"dev\": true\n    },\n    \"node_modules/@types/source-list-map\": {\n      \"version\": \"0.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz\",\n      \"integrity\": \"sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==\",\n      \"dev\": true\n    },\n    \"node_modules/@types/tapable\": {\n      \"version\": \"1.0.6\",\n      \"resolved\": \"https://registry.npmjs.org/@types/tapable/-/tapable-1.0.6.tgz\",\n      \"integrity\": \"sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==\",\n      \"dev\": true\n    },\n    \"node_modules/@types/uglify-js\": {\n      \"version\": \"3.11.1\",\n      \"resolved\": \"https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.11.1.tgz\",\n      \"integrity\": \"sha512-7npvPKV+jINLu1SpSYVWG8KvyJBhBa8tmzMMdDoVc2pWUYHN8KIXlPJhjJ4LT97c4dXJA2SHL/q6ADbDriZN+Q==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"source-map\": \"^0.6.1\"\n      }\n    },\n    \"node_modules/@types/uglify-js/node_modules/source-map\": {\n      \"version\": \"0.6.1\",\n      \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\",\n      \"integrity\": \"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/@types/webpack\": {\n      \"version\": \"4.41.25\",\n      \"resolved\": \"https://registry.npmjs.org/@types/webpack/-/webpack-4.41.25.tgz\",\n      \"integrity\": \"sha512-cr6kZ+4m9lp86ytQc1jPOJXgINQyz3kLLunZ57jznW+WIAL0JqZbGubQk4GlD42MuQL5JGOABrxdpqqWeovlVQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@types/anymatch\": \"*\",\n        \"@types/node\": \"*\",\n        \"@types/tapable\": \"*\",\n        \"@types/uglify-js\": \"*\",\n        \"@types/webpack-sources\": \"*\",\n        \"source-map\": \"^0.6.0\"\n      }\n    },\n    \"node_modules/@types/webpack-sources\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-2.0.0.tgz\",\n      \"integrity\": \"sha512-a5kPx98CNFRKQ+wqawroFunvFqv7GHm/3KOI52NY9xWADgc8smu4R6prt4EU/M4QfVjvgBkMqU4fBhw3QfMVkg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@types/node\": \"*\",\n        \"@types/source-list-map\": \"*\",\n        \"source-map\": \"^0.7.3\"\n      }\n    },\n    \"node_modules/@types/webpack-sources/node_modules/source-map\": {\n      \"version\": \"0.7.3\",\n      \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz\",\n      \"integrity\": \"sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">= 8\"\n      }\n    },\n    \"node_modules/@types/webpack/node_modules/source-map\": {\n      \"version\": \"0.6.1\",\n      \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\",\n      \"integrity\": \"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/@webassemblyjs/ast\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz\",\n      \"integrity\": \"sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@webassemblyjs/helper-module-context\": \"1.9.0\",\n        \"@webassemblyjs/helper-wasm-bytecode\": \"1.9.0\",\n        \"@webassemblyjs/wast-parser\": \"1.9.0\"\n      }\n    },\n    \"node_modules/@webassemblyjs/floating-point-hex-parser\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz\",\n      \"integrity\": \"sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==\",\n      \"dev\": true\n    },\n    \"node_modules/@webassemblyjs/helper-api-error\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz\",\n      \"integrity\": \"sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==\",\n      \"dev\": true\n    },\n    \"node_modules/@webassemblyjs/helper-buffer\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz\",\n      \"integrity\": \"sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==\",\n      \"dev\": true\n    },\n    \"node_modules/@webassemblyjs/helper-code-frame\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz\",\n      \"integrity\": \"sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@webassemblyjs/wast-printer\": \"1.9.0\"\n      }\n    },\n    \"node_modules/@webassemblyjs/helper-fsm\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz\",\n      \"integrity\": \"sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==\",\n      \"dev\": true\n    },\n    \"node_modules/@webassemblyjs/helper-module-context\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz\",\n      \"integrity\": \"sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@webassemblyjs/ast\": \"1.9.0\"\n      }\n    },\n    \"node_modules/@webassemblyjs/helper-wasm-bytecode\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz\",\n      \"integrity\": \"sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==\",\n      \"dev\": true\n    },\n    \"node_modules/@webassemblyjs/helper-wasm-section\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz\",\n      \"integrity\": \"sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/helper-buffer\": \"1.9.0\",\n        \"@webassemblyjs/helper-wasm-bytecode\": \"1.9.0\",\n        \"@webassemblyjs/wasm-gen\": \"1.9.0\"\n      }\n    },\n    \"node_modules/@webassemblyjs/ieee754\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz\",\n      \"integrity\": \"sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@xtuc/ieee754\": \"^1.2.0\"\n      }\n    },\n    \"node_modules/@webassemblyjs/leb128\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz\",\n      \"integrity\": \"sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@xtuc/long\": \"4.2.2\"\n      }\n    },\n    \"node_modules/@webassemblyjs/utf8\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz\",\n      \"integrity\": \"sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==\",\n      \"dev\": true\n    },\n    \"node_modules/@webassemblyjs/wasm-edit\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz\",\n      \"integrity\": \"sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/helper-buffer\": \"1.9.0\",\n        \"@webassemblyjs/helper-wasm-bytecode\": \"1.9.0\",\n        \"@webassemblyjs/helper-wasm-section\": \"1.9.0\",\n        \"@webassemblyjs/wasm-gen\": \"1.9.0\",\n        \"@webassemblyjs/wasm-opt\": \"1.9.0\",\n        \"@webassemblyjs/wasm-parser\": \"1.9.0\",\n        \"@webassemblyjs/wast-printer\": \"1.9.0\"\n      }\n    },\n    \"node_modules/@webassemblyjs/wasm-gen\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz\",\n      \"integrity\": \"sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/helper-wasm-bytecode\": \"1.9.0\",\n        \"@webassemblyjs/ieee754\": \"1.9.0\",\n        \"@webassemblyjs/leb128\": \"1.9.0\",\n        \"@webassemblyjs/utf8\": \"1.9.0\"\n      }\n    },\n    \"node_modules/@webassemblyjs/wasm-opt\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz\",\n      \"integrity\": \"sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/helper-buffer\": \"1.9.0\",\n        \"@webassemblyjs/wasm-gen\": \"1.9.0\",\n        \"@webassemblyjs/wasm-parser\": \"1.9.0\"\n      }\n    },\n    \"node_modules/@webassemblyjs/wasm-parser\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz\",\n      \"integrity\": \"sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/helper-api-error\": \"1.9.0\",\n        \"@webassemblyjs/helper-wasm-bytecode\": \"1.9.0\",\n        \"@webassemblyjs/ieee754\": \"1.9.0\",\n        \"@webassemblyjs/leb128\": \"1.9.0\",\n        \"@webassemblyjs/utf8\": \"1.9.0\"\n      }\n    },\n    \"node_modules/@webassemblyjs/wast-parser\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz\",\n      \"integrity\": \"sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/floating-point-hex-parser\": \"1.9.0\",\n        \"@webassemblyjs/helper-api-error\": \"1.9.0\",\n        \"@webassemblyjs/helper-code-frame\": \"1.9.0\",\n        \"@webassemblyjs/helper-fsm\": \"1.9.0\",\n        \"@xtuc/long\": \"4.2.2\"\n      }\n    },\n    \"node_modules/@webassemblyjs/wast-printer\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz\",\n      \"integrity\": \"sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/wast-parser\": \"1.9.0\",\n        \"@xtuc/long\": \"4.2.2\"\n      }\n    },\n    \"node_modules/@webpack-cli/info\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webpack-cli/info/-/info-1.1.0.tgz\",\n      \"integrity\": \"sha512-uNWSdaYHc+f3LdIZNwhdhkjjLDDl3jP2+XBqAq9H8DjrJUvlOKdP8TNruy1yEaDfgpAIgbSAN7pye4FEHg9tYQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"envinfo\": \"^7.7.3\"\n      },\n      \"peerDependencies\": {\n        \"webpack-cli\": \"4.x.x\"\n      }\n    },\n    \"node_modules/@webpack-cli/serve\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.1.0.tgz\",\n      \"integrity\": \"sha512-7RfnMXCpJ/NThrhq4gYQYILB18xWyoQcBey81oIyVbmgbc6m5ZHHyFK+DyH7pLHJf0p14MxL4mTsoPAgBSTpIg==\",\n      \"dev\": true,\n      \"peerDependencies\": {\n        \"webpack-cli\": \"4.x.x\"\n      },\n      \"peerDependenciesMeta\": {\n        \"webpack-dev-server\": {\n          \"optional\": true\n        }\n      }\n    },\n    \"node_modules/@xtuc/ieee754\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz\",\n      \"integrity\": \"sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==\",\n      \"dev\": true\n    },\n    \"node_modules/@xtuc/long\": {\n      \"version\": \"4.2.2\",\n      \"resolved\": \"https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz\",\n      \"integrity\": \"sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==\",\n      \"dev\": true\n    },\n    \"node_modules/acorn\": {\n      \"version\": \"7.4.1\",\n      \"resolved\": \"https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz\",\n      \"integrity\": \"sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==\",\n      \"dev\": true,\n      \"bin\": {\n        \"acorn\": \"bin/acorn\"\n      },\n      \"engines\": {\n        \"node\": \">=0.4.0\"\n      }\n    },\n    \"node_modules/acorn-jsx\": {\n      \"version\": \"5.3.1\",\n      \"resolved\": \"https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz\",\n      \"integrity\": \"sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==\",\n      \"dev\": true,\n      \"peerDependencies\": {\n        \"acorn\": \"^6.0.0 || ^7.0.0 || ^8.0.0\"\n      }\n    },\n    \"node_modules/acorn-walk\": {\n      \"version\": \"8.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz\",\n      \"integrity\": \"sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==\",\n      \"dev\": true,\n      \"peer\": true,\n      \"engines\": {\n        \"node\": \">=0.4.0\"\n      }\n    },\n    \"node_modules/ajv\": {\n      \"version\": \"6.12.6\",\n      \"resolved\": \"https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz\",\n      \"integrity\": \"sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"fast-deep-equal\": \"^3.1.1\",\n        \"fast-json-stable-stringify\": \"^2.0.0\",\n        \"json-schema-traverse\": \"^0.4.1\",\n        \"uri-js\": \"^4.2.2\"\n      },\n      \"funding\": {\n        \"type\": \"github\",\n        \"url\": \"https://github.com/sponsors/epoberezkin\"\n      }\n    },\n    \"node_modules/ajv-keywords\": {\n      \"version\": \"3.5.2\",\n      \"resolved\": \"https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz\",\n      \"integrity\": \"sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==\",\n      \"dev\": true,\n      \"peerDependencies\": {\n        \"ajv\": \"^6.9.1\"\n      }\n    },\n    \"node_modules/ansi-colors\": {\n      \"version\": \"4.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz\",\n      \"integrity\": \"sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/ansi-regex\": {\n      \"version\": \"5.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz\",\n      \"integrity\": \"sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/ansi-styles\": {\n      \"version\": \"3.2.1\",\n      \"resolved\": \"https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz\",\n      \"integrity\": \"sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"color-convert\": \"^1.9.0\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/anymatch\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz\",\n      \"integrity\": \"sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"normalize-path\": \"^3.0.0\",\n        \"picomatch\": \"^2.0.4\"\n      },\n      \"engines\": {\n        \"node\": \">= 8\"\n      }\n    },\n    \"node_modules/arg\": {\n      \"version\": \"4.1.3\",\n      \"resolved\": \"https://registry.npmjs.org/arg/-/arg-4.1.3.tgz\",\n      \"integrity\": \"sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"node_modules/argparse\": {\n      \"version\": \"1.0.10\",\n      \"resolved\": \"https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz\",\n      \"integrity\": \"sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"sprintf-js\": \"~1.0.2\"\n      }\n    },\n    \"node_modules/array-back\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/array-back/-/array-back-4.0.1.tgz\",\n      \"integrity\": \"sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/array-includes\": {\n      \"version\": \"3.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/array-includes/-/array-includes-3.1.2.tgz\",\n      \"integrity\": \"sha512-w2GspexNQpx+PutG3QpT437/BenZBj0M/MZGn5mzv/MofYqo0xmRHzn4lFsoDlWJ+THYsGJmFlW68WlDFx7VRw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"call-bind\": \"^1.0.0\",\n        \"define-properties\": \"^1.1.3\",\n        \"es-abstract\": \"^1.18.0-next.1\",\n        \"get-intrinsic\": \"^1.0.1\",\n        \"is-string\": \"^1.0.5\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/array.prototype.flat\": {\n      \"version\": \"1.2.4\",\n      \"resolved\": \"https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz\",\n      \"integrity\": \"sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"call-bind\": \"^1.0.0\",\n        \"define-properties\": \"^1.1.3\",\n        \"es-abstract\": \"^1.18.0-next.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/astral-regex\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz\",\n      \"integrity\": \"sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/autoprefixer\": {\n      \"version\": \"10.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.0.2.tgz\",\n      \"integrity\": \"sha512-okBmu9OMdt6DNEcZmnl0IYVv8Xl/xYWRSnc2OJ9UJEOt1u30opG1B8aLsViqKryBaYv1SKB4f85fOGZs5zYxHQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"browserslist\": \"^4.14.7\",\n        \"caniuse-lite\": \"^1.0.30001157\",\n        \"colorette\": \"^1.2.1\",\n        \"normalize-range\": \"^0.1.2\",\n        \"num2fraction\": \"^1.2.2\",\n        \"postcss-value-parser\": \"^4.1.0\"\n      },\n      \"bin\": {\n        \"autoprefixer\": \"bin/autoprefixer\"\n      },\n      \"engines\": {\n        \"node\": \"^10 || ^12 || >=14\"\n      },\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/postcss/\"\n      },\n      \"peerDependencies\": {\n        \"postcss\": \"^8.1.0\"\n      }\n    },\n    \"node_modules/babel-loader\": {\n      \"version\": \"8.2.1\",\n      \"resolved\": \"https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.1.tgz\",\n      \"integrity\": \"sha512-dMF8sb2KQ8kJl21GUjkW1HWmcsL39GOV5vnzjqrCzEPNY0S0UfMLnumidiwIajDSBmKhYf5iRW+HXaM4cvCKBw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"find-cache-dir\": \"^2.1.0\",\n        \"loader-utils\": \"^1.4.0\",\n        \"make-dir\": \"^2.1.0\",\n        \"pify\": \"^4.0.1\",\n        \"schema-utils\": \"^2.6.5\"\n      },\n      \"engines\": {\n        \"node\": \">= 8.9\"\n      },\n      \"peerDependencies\": {\n        \"@babel/core\": \"^7.0.0\",\n        \"webpack\": \">=2\"\n      }\n    },\n    \"node_modules/babel-plugin-dynamic-import-node\": {\n      \"version\": \"2.3.3\",\n      \"resolved\": \"https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz\",\n      \"integrity\": \"sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"object.assign\": \"^4.1.0\"\n      }\n    },\n    \"node_modules/balanced-match\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz\",\n      \"integrity\": \"sha1-ibTRmasr7kneFk6gK4nORi1xt2c=\",\n      \"dev\": true\n    },\n    \"node_modules/big.js\": {\n      \"version\": \"5.2.2\",\n      \"resolved\": \"https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz\",\n      \"integrity\": \"sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \"*\"\n      }\n    },\n    \"node_modules/binary-extensions\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz\",\n      \"integrity\": \"sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/boolbase\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz\",\n      \"integrity\": \"sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==\",\n      \"dev\": true\n    },\n    \"node_modules/bootstrap\": {\n      \"version\": \"4.5.3\",\n      \"resolved\": \"https://registry.npmjs.org/bootstrap/-/bootstrap-4.5.3.tgz\",\n      \"integrity\": \"sha512-o9ppKQioXGqhw8Z7mah6KdTYpNQY//tipnkxppWhPbiSWdD+1raYsnhwEZjkTHYbGee4cVQ0Rx65EhOY/HNLcQ==\",\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/bootstrap\"\n      },\n      \"peerDependencies\": {\n        \"jquery\": \"1.9.1 - 3\",\n        \"popper.js\": \"^1.16.1\"\n      }\n    },\n    \"node_modules/bootswatch\": {\n      \"version\": \"4.5.3\",\n      \"resolved\": \"https://registry.npmjs.org/bootswatch/-/bootswatch-4.5.3.tgz\",\n      \"integrity\": \"sha512-gaB3gBSAegmYbk97aVELKcSKVdPjWsSY4yCITkUt/SqbqjtMU/HtIUszb4O9vzdbrfuVXThc/qCXzjoJaAPgiQ==\"\n    },\n    \"node_modules/brace-expansion\": {\n      \"version\": \"1.1.11\",\n      \"resolved\": \"https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz\",\n      \"integrity\": \"sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"balanced-match\": \"^1.0.0\",\n        \"concat-map\": \"0.0.1\"\n      }\n    },\n    \"node_modules/braces\": {\n      \"version\": \"3.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/braces/-/braces-3.0.2.tgz\",\n      \"integrity\": \"sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"fill-range\": \"^7.0.1\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/browserslist\": {\n      \"version\": \"4.19.1\",\n      \"resolved\": \"https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz\",\n      \"integrity\": \"sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"caniuse-lite\": \"^1.0.30001286\",\n        \"electron-to-chromium\": \"^1.4.17\",\n        \"escalade\": \"^3.1.1\",\n        \"node-releases\": \"^2.0.1\",\n        \"picocolors\": \"^1.0.0\"\n      },\n      \"bin\": {\n        \"browserslist\": \"cli.js\"\n      },\n      \"engines\": {\n        \"node\": \"^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7\"\n      },\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/browserslist\"\n      }\n    },\n    \"node_modules/buffer-from\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz\",\n      \"integrity\": \"sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==\",\n      \"dev\": true\n    },\n    \"node_modules/call-bind\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz\",\n      \"integrity\": \"sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"function-bind\": \"^1.1.1\",\n        \"get-intrinsic\": \"^1.0.0\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/callsites\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz\",\n      \"integrity\": \"sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/camel-case\": {\n      \"version\": \"4.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/camel-case/-/camel-case-4.1.1.tgz\",\n      \"integrity\": \"sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"pascal-case\": \"^3.1.1\",\n        \"tslib\": \"^1.10.0\"\n      }\n    },\n    \"node_modules/camelcase\": {\n      \"version\": \"6.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz\",\n      \"integrity\": \"sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=10\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/sindresorhus\"\n      }\n    },\n    \"node_modules/caniuse-lite\": {\n      \"version\": \"1.0.30001304\",\n      \"resolved\": \"https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001304.tgz\",\n      \"integrity\": \"sha512-bdsfZd6K6ap87AGqSHJP/s1V+U6Z5lyrcbBu3ovbCCf8cSYpwTtGrCBObMpJqwxfTbLW6YTIdbb1jEeTelcpYQ==\",\n      \"dev\": true,\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/browserslist\"\n      }\n    },\n    \"node_modules/chalk\": {\n      \"version\": \"2.4.2\",\n      \"resolved\": \"https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz\",\n      \"integrity\": \"sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"ansi-styles\": \"^3.2.1\",\n        \"escape-string-regexp\": \"^1.0.5\",\n        \"supports-color\": \"^5.3.0\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/chokidar\": {\n      \"version\": \"3.4.3\",\n      \"resolved\": \"https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz\",\n      \"integrity\": \"sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"anymatch\": \"~3.1.1\",\n        \"braces\": \"~3.0.2\",\n        \"glob-parent\": \"~5.1.0\",\n        \"is-binary-path\": \"~2.1.0\",\n        \"is-glob\": \"~4.0.1\",\n        \"normalize-path\": \"~3.0.0\",\n        \"readdirp\": \"~3.5.0\"\n      },\n      \"engines\": {\n        \"node\": \">= 8.10.0\"\n      },\n      \"optionalDependencies\": {\n        \"fsevents\": \"~2.1.2\"\n      }\n    },\n    \"node_modules/chrome-trace-event\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz\",\n      \"integrity\": \"sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"tslib\": \"^1.9.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6.0\"\n      }\n    },\n    \"node_modules/clean-css\": {\n      \"version\": \"4.2.3\",\n      \"resolved\": \"https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz\",\n      \"integrity\": \"sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"source-map\": \"~0.6.0\"\n      },\n      \"engines\": {\n        \"node\": \">= 4.0\"\n      }\n    },\n    \"node_modules/clean-css/node_modules/source-map\": {\n      \"version\": \"0.6.1\",\n      \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\",\n      \"integrity\": \"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/color-convert\": {\n      \"version\": \"1.9.3\",\n      \"resolved\": \"https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz\",\n      \"integrity\": \"sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"color-name\": \"1.1.3\"\n      }\n    },\n    \"node_modules/color-name\": {\n      \"version\": \"1.1.3\",\n      \"resolved\": \"https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz\",\n      \"integrity\": \"sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=\",\n      \"dev\": true\n    },\n    \"node_modules/colorette\": {\n      \"version\": \"1.2.2\",\n      \"resolved\": \"https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz\",\n      \"integrity\": \"sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==\",\n      \"dev\": true\n    },\n    \"node_modules/command-line-usage\": {\n      \"version\": \"6.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.1.tgz\",\n      \"integrity\": \"sha512-F59pEuAR9o1SF/bD0dQBDluhpT4jJQNWUHEuVBqpDmCUo6gPjCi+m9fCWnWZVR/oG6cMTUms4h+3NPl74wGXvA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"array-back\": \"^4.0.1\",\n        \"chalk\": \"^2.4.2\",\n        \"table-layout\": \"^1.0.1\",\n        \"typical\": \"^5.2.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8.0.0\"\n      }\n    },\n    \"node_modules/commander\": {\n      \"version\": \"4.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/commander/-/commander-4.1.1.tgz\",\n      \"integrity\": \"sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">= 6\"\n      }\n    },\n    \"node_modules/comment-parser\": {\n      \"version\": \"0.7.6\",\n      \"resolved\": \"https://registry.npmjs.org/comment-parser/-/comment-parser-0.7.6.tgz\",\n      \"integrity\": \"sha512-GKNxVA7/iuTnAqGADlTWX4tkhzxZKXp5fLJqKTlQLHkE65XDUKutZ3BHaJC5IGcper2tT3QRD1xr4o3jNpgXXg==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">= 6.0.0\"\n      }\n    },\n    \"node_modules/commondir\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz\",\n      \"integrity\": \"sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=\",\n      \"dev\": true\n    },\n    \"node_modules/concat-map\": {\n      \"version\": \"0.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz\",\n      \"integrity\": \"sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=\",\n      \"dev\": true\n    },\n    \"node_modules/contains-path\": {\n      \"version\": \"0.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz\",\n      \"integrity\": \"sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/convert-source-map\": {\n      \"version\": \"1.7.0\",\n      \"resolved\": \"https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz\",\n      \"integrity\": \"sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"safe-buffer\": \"~5.1.1\"\n      }\n    },\n    \"node_modules/core-js\": {\n      \"version\": \"3.7.0\",\n      \"resolved\": \"https://registry.npmjs.org/core-js/-/core-js-3.7.0.tgz\",\n      \"integrity\": \"sha512-NwS7fI5M5B85EwpWuIwJN4i/fbisQUwLwiSNUWeXlkAZ0sbBjLEvLvFLf1uzAUV66PcEPt4xCGCmOZSxVf3xzA==\",\n      \"dev\": true,\n      \"hasInstallScript\": true,\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/core-js\"\n      }\n    },\n    \"node_modules/core-js-compat\": {\n      \"version\": \"3.7.0\",\n      \"resolved\": \"https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.7.0.tgz\",\n      \"integrity\": \"sha512-V8yBI3+ZLDVomoWICO6kq/CD28Y4r1M7CWeO4AGpMdMfseu8bkSubBmUPySMGKRTS+su4XQ07zUkAsiu9FCWTg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"browserslist\": \"^4.14.6\",\n        \"semver\": \"7.0.0\"\n      },\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/core-js\"\n      }\n    },\n    \"node_modules/core-js-compat/node_modules/semver\": {\n      \"version\": \"7.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/semver/-/semver-7.0.0.tgz\",\n      \"integrity\": \"sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==\",\n      \"dev\": true,\n      \"bin\": {\n        \"semver\": \"bin/semver.js\"\n      }\n    },\n    \"node_modules/cosmiconfig\": {\n      \"version\": \"8.1.3\",\n      \"resolved\": \"https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz\",\n      \"integrity\": \"sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"import-fresh\": \"^3.2.1\",\n        \"js-yaml\": \"^4.1.0\",\n        \"parse-json\": \"^5.0.0\",\n        \"path-type\": \"^4.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=14\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/d-fischer\"\n      }\n    },\n    \"node_modules/cosmiconfig-typescript-loader\": {\n      \"version\": \"4.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.3.0.tgz\",\n      \"integrity\": \"sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=12\",\n        \"npm\": \">=6\"\n      },\n      \"peerDependencies\": {\n        \"@types/node\": \"*\",\n        \"cosmiconfig\": \">=7\",\n        \"ts-node\": \">=10\",\n        \"typescript\": \">=3\"\n      }\n    },\n    \"node_modules/cosmiconfig/node_modules/argparse\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz\",\n      \"integrity\": \"sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==\",\n      \"dev\": true\n    },\n    \"node_modules/cosmiconfig/node_modules/js-yaml\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz\",\n      \"integrity\": \"sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"argparse\": \"^2.0.1\"\n      },\n      \"bin\": {\n        \"js-yaml\": \"bin/js-yaml.js\"\n      }\n    },\n    \"node_modules/create-require\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz\",\n      \"integrity\": \"sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"node_modules/cross-spawn\": {\n      \"version\": \"7.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz\",\n      \"integrity\": \"sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"path-key\": \"^3.1.0\",\n        \"shebang-command\": \"^2.0.0\",\n        \"which\": \"^2.0.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 8\"\n      }\n    },\n    \"node_modules/css-loader\": {\n      \"version\": \"5.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/css-loader/-/css-loader-5.0.1.tgz\",\n      \"integrity\": \"sha512-cXc2ti9V234cq7rJzFKhirb2L2iPy8ZjALeVJAozXYz9te3r4eqLSixNAbMDJSgJEQywqXzs8gonxaboeKqwiw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"camelcase\": \"^6.2.0\",\n        \"cssesc\": \"^3.0.0\",\n        \"icss-utils\": \"^5.0.0\",\n        \"loader-utils\": \"^2.0.0\",\n        \"postcss\": \"^8.1.4\",\n        \"postcss-modules-extract-imports\": \"^3.0.0\",\n        \"postcss-modules-local-by-default\": \"^4.0.0\",\n        \"postcss-modules-scope\": \"^3.0.0\",\n        \"postcss-modules-values\": \"^4.0.0\",\n        \"postcss-value-parser\": \"^4.1.0\",\n        \"schema-utils\": \"^3.0.0\",\n        \"semver\": \"^7.3.2\"\n      },\n      \"engines\": {\n        \"node\": \">= 10.13.0\"\n      },\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/webpack\"\n      },\n      \"peerDependencies\": {\n        \"webpack\": \"^4.27.0 || ^5.0.0\"\n      }\n    },\n    \"node_modules/css-loader/node_modules/loader-utils\": {\n      \"version\": \"2.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz\",\n      \"integrity\": \"sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"big.js\": \"^5.2.2\",\n        \"emojis-list\": \"^3.0.0\",\n        \"json5\": \"^2.1.2\"\n      },\n      \"engines\": {\n        \"node\": \">=8.9.0\"\n      }\n    },\n    \"node_modules/css-loader/node_modules/schema-utils\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz\",\n      \"integrity\": \"sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@types/json-schema\": \"^7.0.6\",\n        \"ajv\": \"^6.12.5\",\n        \"ajv-keywords\": \"^3.5.2\"\n      },\n      \"engines\": {\n        \"node\": \">= 10.13.0\"\n      },\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/webpack\"\n      }\n    },\n    \"node_modules/css-loader/node_modules/semver\": {\n      \"version\": \"7.3.2\",\n      \"resolved\": \"https://registry.npmjs.org/semver/-/semver-7.3.2.tgz\",\n      \"integrity\": \"sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==\",\n      \"dev\": true,\n      \"bin\": {\n        \"semver\": \"bin/semver.js\"\n      },\n      \"engines\": {\n        \"node\": \">=10\"\n      }\n    },\n    \"node_modules/css-select\": {\n      \"version\": \"4.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz\",\n      \"integrity\": \"sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"boolbase\": \"^1.0.0\",\n        \"css-what\": \"^6.0.1\",\n        \"domhandler\": \"^4.3.1\",\n        \"domutils\": \"^2.8.0\",\n        \"nth-check\": \"^2.0.1\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/fb55\"\n      }\n    },\n    \"node_modules/css-what\": {\n      \"version\": \"6.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz\",\n      \"integrity\": \"sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">= 6\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/fb55\"\n      }\n    },\n    \"node_modules/cssesc\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz\",\n      \"integrity\": \"sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==\",\n      \"dev\": true,\n      \"bin\": {\n        \"cssesc\": \"bin/cssesc\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/debug\": {\n      \"version\": \"4.3.1\",\n      \"resolved\": \"https://registry.npmjs.org/debug/-/debug-4.3.1.tgz\",\n      \"integrity\": \"sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"ms\": \"2.1.2\"\n      },\n      \"engines\": {\n        \"node\": \">=6.0\"\n      },\n      \"peerDependenciesMeta\": {\n        \"supports-color\": {\n          \"optional\": true\n        }\n      }\n    },\n    \"node_modules/deep-extend\": {\n      \"version\": \"0.6.0\",\n      \"resolved\": \"https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz\",\n      \"integrity\": \"sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=4.0.0\"\n      }\n    },\n    \"node_modules/deep-is\": {\n      \"version\": \"0.1.3\",\n      \"resolved\": \"https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz\",\n      \"integrity\": \"sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=\",\n      \"dev\": true\n    },\n    \"node_modules/define-properties\": {\n      \"version\": \"1.1.3\",\n      \"resolved\": \"https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz\",\n      \"integrity\": \"sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"object-keys\": \"^1.0.12\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      }\n    },\n    \"node_modules/diff\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/diff/-/diff-4.0.2.tgz\",\n      \"integrity\": \"sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==\",\n      \"dev\": true,\n      \"peer\": true,\n      \"engines\": {\n        \"node\": \">=0.3.1\"\n      }\n    },\n    \"node_modules/doctrine\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz\",\n      \"integrity\": \"sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"esutils\": \"^2.0.2\"\n      },\n      \"engines\": {\n        \"node\": \">=6.0.0\"\n      }\n    },\n    \"node_modules/dom-converter\": {\n      \"version\": \"0.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz\",\n      \"integrity\": \"sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"utila\": \"~0.4\"\n      }\n    },\n    \"node_modules/dom-serializer\": {\n      \"version\": \"1.4.1\",\n      \"resolved\": \"https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz\",\n      \"integrity\": \"sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"domelementtype\": \"^2.0.1\",\n        \"domhandler\": \"^4.2.0\",\n        \"entities\": \"^2.0.0\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/cheeriojs/dom-serializer?sponsor=1\"\n      }\n    },\n    \"node_modules/domelementtype\": {\n      \"version\": \"2.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz\",\n      \"integrity\": \"sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==\",\n      \"dev\": true,\n      \"funding\": [\n        {\n          \"type\": \"github\",\n          \"url\": \"https://github.com/sponsors/fb55\"\n        }\n      ]\n    },\n    \"node_modules/domhandler\": {\n      \"version\": \"4.3.1\",\n      \"resolved\": \"https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz\",\n      \"integrity\": \"sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"domelementtype\": \"^2.2.0\"\n      },\n      \"engines\": {\n        \"node\": \">= 4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/fb55/domhandler?sponsor=1\"\n      }\n    },\n    \"node_modules/domutils\": {\n      \"version\": \"2.8.0\",\n      \"resolved\": \"https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz\",\n      \"integrity\": \"sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"dom-serializer\": \"^1.0.1\",\n        \"domelementtype\": \"^2.2.0\",\n        \"domhandler\": \"^4.2.0\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/fb55/domutils?sponsor=1\"\n      }\n    },\n    \"node_modules/dot-case\": {\n      \"version\": \"3.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/dot-case/-/dot-case-3.0.3.tgz\",\n      \"integrity\": \"sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"no-case\": \"^3.0.3\",\n        \"tslib\": \"^1.10.0\"\n      }\n    },\n    \"node_modules/electron-to-chromium\": {\n      \"version\": \"1.4.60\",\n      \"resolved\": \"https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.60.tgz\",\n      \"integrity\": \"sha512-h53hbEiKC6hijelDgxgkgAUC3PKyR7TmIfvjHnBjUGPMg/3sBuTyG6eDormw+lY24uUJvHkUPzB8dpK8b2u3Sw==\",\n      \"dev\": true\n    },\n    \"node_modules/emoji-regex\": {\n      \"version\": \"7.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz\",\n      \"integrity\": \"sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==\",\n      \"dev\": true\n    },\n    \"node_modules/emojis-list\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz\",\n      \"integrity\": \"sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">= 4\"\n      }\n    },\n    \"node_modules/end-of-stream\": {\n      \"version\": \"1.4.4\",\n      \"resolved\": \"https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz\",\n      \"integrity\": \"sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"once\": \"^1.4.0\"\n      }\n    },\n    \"node_modules/enhanced-resolve\": {\n      \"version\": \"5.3.2\",\n      \"resolved\": \"https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.3.2.tgz\",\n      \"integrity\": \"sha512-G28GCrglCAH6+EqMN2D+Q2wCUS1O1vVQJBn8ME2I/Api41YBe4vLWWRBOUbwDH7vwzSZdljxwTRVqnf+sm6XqQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"graceful-fs\": \"^4.2.4\",\n        \"tapable\": \"^2.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=10.13.0\"\n      }\n    },\n    \"node_modules/enhanced-resolve/node_modules/tapable\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/tapable/-/tapable-2.1.1.tgz\",\n      \"integrity\": \"sha512-Wib1S8m2wdpLbmQz0RBEVosIyvb/ykfKXf3ZIDqvWoMg/zTNm6G/tDSuUM61J1kNCDXWJrLHGSFeMhAG+gAGpQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/enquirer\": {\n      \"version\": \"2.3.6\",\n      \"resolved\": \"https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz\",\n      \"integrity\": \"sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"ansi-colors\": \"^4.1.1\"\n      },\n      \"engines\": {\n        \"node\": \">=8.6\"\n      }\n    },\n    \"node_modules/entities\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/entities/-/entities-2.2.0.tgz\",\n      \"integrity\": \"sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==\",\n      \"dev\": true,\n      \"funding\": {\n        \"url\": \"https://github.com/fb55/entities?sponsor=1\"\n      }\n    },\n    \"node_modules/envinfo\": {\n      \"version\": \"7.7.3\",\n      \"resolved\": \"https://registry.npmjs.org/envinfo/-/envinfo-7.7.3.tgz\",\n      \"integrity\": \"sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA==\",\n      \"dev\": true,\n      \"bin\": {\n        \"envinfo\": \"dist/cli.js\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/error-ex\": {\n      \"version\": \"1.3.2\",\n      \"resolved\": \"https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz\",\n      \"integrity\": \"sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"is-arrayish\": \"^0.2.1\"\n      }\n    },\n    \"node_modules/es-abstract\": {\n      \"version\": \"1.18.0-next.1\",\n      \"resolved\": \"https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz\",\n      \"integrity\": \"sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"es-to-primitive\": \"^1.2.1\",\n        \"function-bind\": \"^1.1.1\",\n        \"has\": \"^1.0.3\",\n        \"has-symbols\": \"^1.0.1\",\n        \"is-callable\": \"^1.2.2\",\n        \"is-negative-zero\": \"^2.0.0\",\n        \"is-regex\": \"^1.1.1\",\n        \"object-inspect\": \"^1.8.0\",\n        \"object-keys\": \"^1.1.1\",\n        \"object.assign\": \"^4.1.1\",\n        \"string.prototype.trimend\": \"^1.0.1\",\n        \"string.prototype.trimstart\": \"^1.0.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/es-to-primitive\": {\n      \"version\": \"1.2.1\",\n      \"resolved\": \"https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz\",\n      \"integrity\": \"sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"is-callable\": \"^1.1.4\",\n        \"is-date-object\": \"^1.0.1\",\n        \"is-symbol\": \"^1.0.2\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/escalade\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz\",\n      \"integrity\": \"sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/escape-string-regexp\": {\n      \"version\": \"1.0.5\",\n      \"resolved\": \"https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz\",\n      \"integrity\": \"sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.8.0\"\n      }\n    },\n    \"node_modules/eslint\": {\n      \"version\": \"7.14.0\",\n      \"resolved\": \"https://registry.npmjs.org/eslint/-/eslint-7.14.0.tgz\",\n      \"integrity\": \"sha512-5YubdnPXrlrYAFCKybPuHIAH++PINe1pmKNc5wQRB9HSbqIK1ywAnntE3Wwua4giKu0bjligf1gLF6qxMGOYRA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/code-frame\": \"^7.0.0\",\n        \"@eslint/eslintrc\": \"^0.2.1\",\n        \"ajv\": \"^6.10.0\",\n        \"chalk\": \"^4.0.0\",\n        \"cross-spawn\": \"^7.0.2\",\n        \"debug\": \"^4.0.1\",\n        \"doctrine\": \"^3.0.0\",\n        \"enquirer\": \"^2.3.5\",\n        \"eslint-scope\": \"^5.1.1\",\n        \"eslint-utils\": \"^2.1.0\",\n        \"eslint-visitor-keys\": \"^2.0.0\",\n        \"espree\": \"^7.3.0\",\n        \"esquery\": \"^1.2.0\",\n        \"esutils\": \"^2.0.2\",\n        \"file-entry-cache\": \"^5.0.1\",\n        \"functional-red-black-tree\": \"^1.0.1\",\n        \"glob-parent\": \"^5.0.0\",\n        \"globals\": \"^12.1.0\",\n        \"ignore\": \"^4.0.6\",\n        \"import-fresh\": \"^3.0.0\",\n        \"imurmurhash\": \"^0.1.4\",\n        \"is-glob\": \"^4.0.0\",\n        \"js-yaml\": \"^3.13.1\",\n        \"json-stable-stringify-without-jsonify\": \"^1.0.1\",\n        \"levn\": \"^0.4.1\",\n        \"lodash\": \"^4.17.19\",\n        \"minimatch\": \"^3.0.4\",\n        \"natural-compare\": \"^1.4.0\",\n        \"optionator\": \"^0.9.1\",\n        \"progress\": \"^2.0.0\",\n        \"regexpp\": \"^3.1.0\",\n        \"semver\": \"^7.2.1\",\n        \"strip-ansi\": \"^6.0.0\",\n        \"strip-json-comments\": \"^3.1.0\",\n        \"table\": \"^5.2.3\",\n        \"text-table\": \"^0.2.0\",\n        \"v8-compile-cache\": \"^2.0.3\"\n      },\n      \"bin\": {\n        \"eslint\": \"bin/eslint.js\"\n      },\n      \"engines\": {\n        \"node\": \"^10.12.0 || >=12.0.0\"\n      },\n      \"funding\": {\n        \"url\": \"https://opencollective.com/eslint\"\n      }\n    },\n    \"node_modules/eslint-import-resolver-node\": {\n      \"version\": \"0.3.4\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz\",\n      \"integrity\": \"sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"debug\": \"^2.6.9\",\n        \"resolve\": \"^1.13.1\"\n      }\n    },\n    \"node_modules/eslint-import-resolver-node/node_modules/debug\": {\n      \"version\": \"2.6.9\",\n      \"resolved\": \"https://registry.npmjs.org/debug/-/debug-2.6.9.tgz\",\n      \"integrity\": \"sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"ms\": \"2.0.0\"\n      }\n    },\n    \"node_modules/eslint-import-resolver-node/node_modules/ms\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/ms/-/ms-2.0.0.tgz\",\n      \"integrity\": \"sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=\",\n      \"dev\": true\n    },\n    \"node_modules/eslint-module-utils\": {\n      \"version\": \"2.6.0\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz\",\n      \"integrity\": \"sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"debug\": \"^2.6.9\",\n        \"pkg-dir\": \"^2.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/eslint-module-utils/node_modules/debug\": {\n      \"version\": \"2.6.9\",\n      \"resolved\": \"https://registry.npmjs.org/debug/-/debug-2.6.9.tgz\",\n      \"integrity\": \"sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"ms\": \"2.0.0\"\n      }\n    },\n    \"node_modules/eslint-module-utils/node_modules/ms\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/ms/-/ms-2.0.0.tgz\",\n      \"integrity\": \"sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=\",\n      \"dev\": true\n    },\n    \"node_modules/eslint-plugin-import\": {\n      \"version\": \"2.22.1\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz\",\n      \"integrity\": \"sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"array-includes\": \"^3.1.1\",\n        \"array.prototype.flat\": \"^1.2.3\",\n        \"contains-path\": \"^0.1.0\",\n        \"debug\": \"^2.6.9\",\n        \"doctrine\": \"1.5.0\",\n        \"eslint-import-resolver-node\": \"^0.3.4\",\n        \"eslint-module-utils\": \"^2.6.0\",\n        \"has\": \"^1.0.3\",\n        \"minimatch\": \"^3.0.4\",\n        \"object.values\": \"^1.1.1\",\n        \"read-pkg-up\": \"^2.0.0\",\n        \"resolve\": \"^1.17.0\",\n        \"tsconfig-paths\": \"^3.9.0\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      },\n      \"peerDependencies\": {\n        \"eslint\": \"^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0\"\n      }\n    },\n    \"node_modules/eslint-plugin-import/node_modules/debug\": {\n      \"version\": \"2.6.9\",\n      \"resolved\": \"https://registry.npmjs.org/debug/-/debug-2.6.9.tgz\",\n      \"integrity\": \"sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"ms\": \"2.0.0\"\n      }\n    },\n    \"node_modules/eslint-plugin-import/node_modules/doctrine\": {\n      \"version\": \"1.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz\",\n      \"integrity\": \"sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"esutils\": \"^2.0.2\",\n        \"isarray\": \"^1.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/eslint-plugin-import/node_modules/ms\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/ms/-/ms-2.0.0.tgz\",\n      \"integrity\": \"sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=\",\n      \"dev\": true\n    },\n    \"node_modules/eslint-plugin-jquery\": {\n      \"version\": \"1.5.1\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-plugin-jquery/-/eslint-plugin-jquery-1.5.1.tgz\",\n      \"integrity\": \"sha512-L7v1eaK5t80C0lvUXPFP9MKnBOqPSKhCOYyzy4LZ0+iK+TJwN8S9gAkzzP1AOhypRIwA88HF6phQ9C7jnOpW8w==\",\n      \"dev\": true,\n      \"peerDependencies\": {\n        \"eslint\": \">=5.4.0\"\n      }\n    },\n    \"node_modules/eslint-plugin-jsdoc\": {\n      \"version\": \"30.7.8\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-30.7.8.tgz\",\n      \"integrity\": \"sha512-OWm2AYvXjCl7nRbpcw5xisfSVkpVAyp4lGqL9T+DeK4kaPm6ecnmTc/G5s1PtcRrwbaI8bIWGzwScqv5CdGyxA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"comment-parser\": \"^0.7.6\",\n        \"debug\": \"^4.2.0\",\n        \"jsdoctypeparser\": \"^9.0.0\",\n        \"lodash\": \"^4.17.20\",\n        \"regextras\": \"^0.7.1\",\n        \"semver\": \"^7.3.2\",\n        \"spdx-expression-parse\": \"^3.0.1\"\n      },\n      \"engines\": {\n        \"node\": \">=10\"\n      },\n      \"peerDependencies\": {\n        \"eslint\": \"^6.0.0 || ^7.0.0\"\n      }\n    },\n    \"node_modules/eslint-plugin-jsdoc/node_modules/semver\": {\n      \"version\": \"7.3.2\",\n      \"resolved\": \"https://registry.npmjs.org/semver/-/semver-7.3.2.tgz\",\n      \"integrity\": \"sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==\",\n      \"dev\": true,\n      \"bin\": {\n        \"semver\": \"bin/semver.js\"\n      },\n      \"engines\": {\n        \"node\": \">=10\"\n      }\n    },\n    \"node_modules/eslint-rule-composer\": {\n      \"version\": \"0.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz\",\n      \"integrity\": \"sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=4.0.0\"\n      }\n    },\n    \"node_modules/eslint-scope\": {\n      \"version\": \"5.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz\",\n      \"integrity\": \"sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"esrecurse\": \"^4.1.0\",\n        \"estraverse\": \"^4.1.1\"\n      },\n      \"engines\": {\n        \"node\": \">=8.0.0\"\n      }\n    },\n    \"node_modules/eslint-utils\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz\",\n      \"integrity\": \"sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"eslint-visitor-keys\": \"^1.1.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/mysticatea\"\n      }\n    },\n    \"node_modules/eslint-visitor-keys\": {\n      \"version\": \"1.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz\",\n      \"integrity\": \"sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/eslint/node_modules/ansi-styles\": {\n      \"version\": \"4.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz\",\n      \"integrity\": \"sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"color-convert\": \"^2.0.1\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/chalk/ansi-styles?sponsor=1\"\n      }\n    },\n    \"node_modules/eslint/node_modules/chalk\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz\",\n      \"integrity\": \"sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"ansi-styles\": \"^4.1.0\",\n        \"supports-color\": \"^7.1.0\"\n      },\n      \"engines\": {\n        \"node\": \">=10\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/chalk/chalk?sponsor=1\"\n      }\n    },\n    \"node_modules/eslint/node_modules/color-convert\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz\",\n      \"integrity\": \"sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"color-name\": \"~1.1.4\"\n      },\n      \"engines\": {\n        \"node\": \">=7.0.0\"\n      }\n    },\n    \"node_modules/eslint/node_modules/color-name\": {\n      \"version\": \"1.1.4\",\n      \"resolved\": \"https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz\",\n      \"integrity\": \"sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==\",\n      \"dev\": true\n    },\n    \"node_modules/eslint/node_modules/eslint-scope\": {\n      \"version\": \"5.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz\",\n      \"integrity\": \"sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"esrecurse\": \"^4.3.0\",\n        \"estraverse\": \"^4.1.1\"\n      },\n      \"engines\": {\n        \"node\": \">=8.0.0\"\n      }\n    },\n    \"node_modules/eslint/node_modules/eslint-visitor-keys\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz\",\n      \"integrity\": \"sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=10\"\n      }\n    },\n    \"node_modules/eslint/node_modules/globals\": {\n      \"version\": \"12.4.0\",\n      \"resolved\": \"https://registry.npmjs.org/globals/-/globals-12.4.0.tgz\",\n      \"integrity\": \"sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"type-fest\": \"^0.8.1\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/sindresorhus\"\n      }\n    },\n    \"node_modules/eslint/node_modules/has-flag\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz\",\n      \"integrity\": \"sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/eslint/node_modules/semver\": {\n      \"version\": \"7.3.2\",\n      \"resolved\": \"https://registry.npmjs.org/semver/-/semver-7.3.2.tgz\",\n      \"integrity\": \"sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==\",\n      \"dev\": true,\n      \"bin\": {\n        \"semver\": \"bin/semver.js\"\n      },\n      \"engines\": {\n        \"node\": \">=10\"\n      }\n    },\n    \"node_modules/eslint/node_modules/supports-color\": {\n      \"version\": \"7.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz\",\n      \"integrity\": \"sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"has-flag\": \"^4.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/espree\": {\n      \"version\": \"7.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/espree/-/espree-7.3.0.tgz\",\n      \"integrity\": \"sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"acorn\": \"^7.4.0\",\n        \"acorn-jsx\": \"^5.2.0\",\n        \"eslint-visitor-keys\": \"^1.3.0\"\n      },\n      \"engines\": {\n        \"node\": \"^10.12.0 || >=12.0.0\"\n      }\n    },\n    \"node_modules/esprima\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz\",\n      \"integrity\": \"sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==\",\n      \"dev\": true,\n      \"bin\": {\n        \"esparse\": \"bin/esparse.js\",\n        \"esvalidate\": \"bin/esvalidate.js\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/esquery\": {\n      \"version\": \"1.3.1\",\n      \"resolved\": \"https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz\",\n      \"integrity\": \"sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"estraverse\": \"^5.1.0\"\n      },\n      \"engines\": {\n        \"node\": \">=0.10\"\n      }\n    },\n    \"node_modules/esquery/node_modules/estraverse\": {\n      \"version\": \"5.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz\",\n      \"integrity\": \"sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=4.0\"\n      }\n    },\n    \"node_modules/esrecurse\": {\n      \"version\": \"4.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz\",\n      \"integrity\": \"sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"estraverse\": \"^5.2.0\"\n      },\n      \"engines\": {\n        \"node\": \">=4.0\"\n      }\n    },\n    \"node_modules/esrecurse/node_modules/estraverse\": {\n      \"version\": \"5.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz\",\n      \"integrity\": \"sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=4.0\"\n      }\n    },\n    \"node_modules/estraverse\": {\n      \"version\": \"4.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz\",\n      \"integrity\": \"sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=4.0\"\n      }\n    },\n    \"node_modules/esutils\": {\n      \"version\": \"2.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz\",\n      \"integrity\": \"sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/events\": {\n      \"version\": \"3.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/events/-/events-3.2.0.tgz\",\n      \"integrity\": \"sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.8.x\"\n      }\n    },\n    \"node_modules/execa\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/execa/-/execa-4.1.0.tgz\",\n      \"integrity\": \"sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"cross-spawn\": \"^7.0.0\",\n        \"get-stream\": \"^5.0.0\",\n        \"human-signals\": \"^1.1.1\",\n        \"is-stream\": \"^2.0.0\",\n        \"merge-stream\": \"^2.0.0\",\n        \"npm-run-path\": \"^4.0.0\",\n        \"onetime\": \"^5.1.0\",\n        \"signal-exit\": \"^3.0.2\",\n        \"strip-final-newline\": \"^2.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=10\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sindresorhus/execa?sponsor=1\"\n      }\n    },\n    \"node_modules/fast-deep-equal\": {\n      \"version\": \"3.1.3\",\n      \"resolved\": \"https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz\",\n      \"integrity\": \"sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==\",\n      \"dev\": true\n    },\n    \"node_modules/fast-json-stable-stringify\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz\",\n      \"integrity\": \"sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==\",\n      \"dev\": true\n    },\n    \"node_modules/fast-levenshtein\": {\n      \"version\": \"2.0.6\",\n      \"resolved\": \"https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz\",\n      \"integrity\": \"sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=\",\n      \"dev\": true\n    },\n    \"node_modules/file-entry-cache\": {\n      \"version\": \"5.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz\",\n      \"integrity\": \"sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"flat-cache\": \"^2.0.1\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/fill-range\": {\n      \"version\": \"7.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz\",\n      \"integrity\": \"sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"to-regex-range\": \"^5.0.1\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/find-cache-dir\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz\",\n      \"integrity\": \"sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"commondir\": \"^1.0.1\",\n        \"make-dir\": \"^2.0.0\",\n        \"pkg-dir\": \"^3.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/find-cache-dir/node_modules/find-up\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz\",\n      \"integrity\": \"sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"locate-path\": \"^3.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/find-cache-dir/node_modules/locate-path\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz\",\n      \"integrity\": \"sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"p-locate\": \"^3.0.0\",\n        \"path-exists\": \"^3.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/find-cache-dir/node_modules/p-limit\": {\n      \"version\": \"2.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz\",\n      \"integrity\": \"sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"p-try\": \"^2.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/sindresorhus\"\n      }\n    },\n    \"node_modules/find-cache-dir/node_modules/p-locate\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz\",\n      \"integrity\": \"sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"p-limit\": \"^2.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/find-cache-dir/node_modules/p-try\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz\",\n      \"integrity\": \"sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/find-cache-dir/node_modules/pkg-dir\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz\",\n      \"integrity\": \"sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"find-up\": \"^3.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/find-up\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz\",\n      \"integrity\": \"sha1-RdG35QbHF93UgndaK3eSCjwMV6c=\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"locate-path\": \"^2.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/flat-cache\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz\",\n      \"integrity\": \"sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"flatted\": \"^2.0.0\",\n        \"rimraf\": \"2.6.3\",\n        \"write\": \"1.0.3\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/flatted\": {\n      \"version\": \"2.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz\",\n      \"integrity\": \"sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==\",\n      \"dev\": true\n    },\n    \"node_modules/fs.realpath\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz\",\n      \"integrity\": \"sha1-FQStJSMVjKpA20onh8sBQRmU6k8=\",\n      \"dev\": true\n    },\n    \"node_modules/fsevents\": {\n      \"version\": \"2.1.3\",\n      \"resolved\": \"https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz\",\n      \"integrity\": \"sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==\",\n      \"dev\": true,\n      \"hasInstallScript\": true,\n      \"optional\": true,\n      \"os\": [\n        \"darwin\"\n      ],\n      \"engines\": {\n        \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\"\n      }\n    },\n    \"node_modules/function-bind\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz\",\n      \"integrity\": \"sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==\",\n      \"dev\": true\n    },\n    \"node_modules/functional-red-black-tree\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz\",\n      \"integrity\": \"sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=\",\n      \"dev\": true\n    },\n    \"node_modules/gensync\": {\n      \"version\": \"1.0.0-beta.2\",\n      \"resolved\": \"https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz\",\n      \"integrity\": \"sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6.9.0\"\n      }\n    },\n    \"node_modules/get-intrinsic\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz\",\n      \"integrity\": \"sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"function-bind\": \"^1.1.1\",\n        \"has\": \"^1.0.3\",\n        \"has-symbols\": \"^1.0.1\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/get-stream\": {\n      \"version\": \"5.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz\",\n      \"integrity\": \"sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"pump\": \"^3.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/sindresorhus\"\n      }\n    },\n    \"node_modules/glob\": {\n      \"version\": \"7.1.6\",\n      \"resolved\": \"https://registry.npmjs.org/glob/-/glob-7.1.6.tgz\",\n      \"integrity\": \"sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"fs.realpath\": \"^1.0.0\",\n        \"inflight\": \"^1.0.4\",\n        \"inherits\": \"2\",\n        \"minimatch\": \"^3.0.4\",\n        \"once\": \"^1.3.0\",\n        \"path-is-absolute\": \"^1.0.0\"\n      },\n      \"engines\": {\n        \"node\": \"*\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/isaacs\"\n      }\n    },\n    \"node_modules/glob-parent\": {\n      \"version\": \"5.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz\",\n      \"integrity\": \"sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"is-glob\": \"^4.0.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 6\"\n      }\n    },\n    \"node_modules/glob-to-regexp\": {\n      \"version\": \"0.4.1\",\n      \"resolved\": \"https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz\",\n      \"integrity\": \"sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==\",\n      \"dev\": true\n    },\n    \"node_modules/globals\": {\n      \"version\": \"11.12.0\",\n      \"resolved\": \"https://registry.npmjs.org/globals/-/globals-11.12.0.tgz\",\n      \"integrity\": \"sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/graceful-fs\": {\n      \"version\": \"4.2.4\",\n      \"resolved\": \"https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz\",\n      \"integrity\": \"sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==\",\n      \"dev\": true\n    },\n    \"node_modules/has\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/has/-/has-1.0.3.tgz\",\n      \"integrity\": \"sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"function-bind\": \"^1.1.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4.0\"\n      }\n    },\n    \"node_modules/has-flag\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz\",\n      \"integrity\": \"sha1-tdRU3CGZriJWmfNGfloH87lVuv0=\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/has-symbols\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz\",\n      \"integrity\": \"sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/he\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/he/-/he-1.2.0.tgz\",\n      \"integrity\": \"sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==\",\n      \"dev\": true,\n      \"bin\": {\n        \"he\": \"bin/he\"\n      }\n    },\n    \"node_modules/hosted-git-info\": {\n      \"version\": \"2.8.9\",\n      \"resolved\": \"https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz\",\n      \"integrity\": \"sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==\",\n      \"dev\": true\n    },\n    \"node_modules/html-minifier-terser\": {\n      \"version\": \"5.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz\",\n      \"integrity\": \"sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"camel-case\": \"^4.1.1\",\n        \"clean-css\": \"^4.2.3\",\n        \"commander\": \"^4.1.1\",\n        \"he\": \"^1.2.0\",\n        \"param-case\": \"^3.0.3\",\n        \"relateurl\": \"^0.2.7\",\n        \"terser\": \"^4.6.3\"\n      },\n      \"bin\": {\n        \"html-minifier-terser\": \"cli.js\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/html-webpack-plugin\": {\n      \"version\": \"4.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz\",\n      \"integrity\": \"sha512-MouoXEYSjTzCrjIxWwg8gxL5fE2X2WZJLmBYXlaJhQUH5K/b5OrqmV7T4dB7iu0xkmJ6JlUuV6fFVtnqbPopZw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@types/html-minifier-terser\": \"^5.0.0\",\n        \"@types/tapable\": \"^1.0.5\",\n        \"@types/webpack\": \"^4.41.8\",\n        \"html-minifier-terser\": \"^5.0.1\",\n        \"loader-utils\": \"^1.2.3\",\n        \"lodash\": \"^4.17.15\",\n        \"pretty-error\": \"^2.1.1\",\n        \"tapable\": \"^1.1.3\",\n        \"util.promisify\": \"1.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6.9\"\n      },\n      \"peerDependencies\": {\n        \"webpack\": \"^4.0.0 || ^5.0.0\"\n      }\n    },\n    \"node_modules/htmlparser2\": {\n      \"version\": \"6.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz\",\n      \"integrity\": \"sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==\",\n      \"dev\": true,\n      \"funding\": [\n        \"https://github.com/fb55/htmlparser2?sponsor=1\",\n        {\n          \"type\": \"github\",\n          \"url\": \"https://github.com/sponsors/fb55\"\n        }\n      ],\n      \"dependencies\": {\n        \"domelementtype\": \"^2.0.1\",\n        \"domhandler\": \"^4.0.0\",\n        \"domutils\": \"^2.5.2\",\n        \"entities\": \"^2.0.0\"\n      }\n    },\n    \"node_modules/human-signals\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz\",\n      \"integrity\": \"sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=8.12.0\"\n      }\n    },\n    \"node_modules/icss-utils\": {\n      \"version\": \"5.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz\",\n      \"integrity\": \"sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \"^10 || ^12 || >= 14\"\n      },\n      \"peerDependencies\": {\n        \"postcss\": \"^8.1.0\"\n      }\n    },\n    \"node_modules/ignore\": {\n      \"version\": \"4.0.6\",\n      \"resolved\": \"https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz\",\n      \"integrity\": \"sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">= 4\"\n      }\n    },\n    \"node_modules/import-fresh\": {\n      \"version\": \"3.2.2\",\n      \"resolved\": \"https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz\",\n      \"integrity\": \"sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"parent-module\": \"^1.0.0\",\n        \"resolve-from\": \"^4.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/import-local\": {\n      \"version\": \"3.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz\",\n      \"integrity\": \"sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"pkg-dir\": \"^4.2.0\",\n        \"resolve-cwd\": \"^3.0.0\"\n      },\n      \"bin\": {\n        \"import-local-fixture\": \"fixtures/cli.js\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/import-local/node_modules/find-up\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz\",\n      \"integrity\": \"sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"locate-path\": \"^5.0.0\",\n        \"path-exists\": \"^4.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/import-local/node_modules/locate-path\": {\n      \"version\": \"5.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz\",\n      \"integrity\": \"sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"p-locate\": \"^4.1.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/import-local/node_modules/p-limit\": {\n      \"version\": \"2.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz\",\n      \"integrity\": \"sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"p-try\": \"^2.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/sindresorhus\"\n      }\n    },\n    \"node_modules/import-local/node_modules/p-locate\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz\",\n      \"integrity\": \"sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"p-limit\": \"^2.2.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/import-local/node_modules/p-try\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz\",\n      \"integrity\": \"sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/import-local/node_modules/path-exists\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz\",\n      \"integrity\": \"sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/import-local/node_modules/pkg-dir\": {\n      \"version\": \"4.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz\",\n      \"integrity\": \"sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"find-up\": \"^4.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/imurmurhash\": {\n      \"version\": \"0.1.4\",\n      \"resolved\": \"https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz\",\n      \"integrity\": \"sha1-khi5srkoojixPcT7a21XbyMUU+o=\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.8.19\"\n      }\n    },\n    \"node_modules/indexes-of\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz\",\n      \"integrity\": \"sha1-8w9xbI4r00bHtn0985FVZqfAVgc=\",\n      \"dev\": true\n    },\n    \"node_modules/inflight\": {\n      \"version\": \"1.0.6\",\n      \"resolved\": \"https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz\",\n      \"integrity\": \"sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"once\": \"^1.3.0\",\n        \"wrappy\": \"1\"\n      }\n    },\n    \"node_modules/inherits\": {\n      \"version\": \"2.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz\",\n      \"integrity\": \"sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==\",\n      \"dev\": true\n    },\n    \"node_modules/interpret\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz\",\n      \"integrity\": \"sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">= 0.10\"\n      }\n    },\n    \"node_modules/is-arrayish\": {\n      \"version\": \"0.2.1\",\n      \"resolved\": \"https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz\",\n      \"integrity\": \"sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=\",\n      \"dev\": true\n    },\n    \"node_modules/is-binary-path\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz\",\n      \"integrity\": \"sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"binary-extensions\": \"^2.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/is-callable\": {\n      \"version\": \"1.2.2\",\n      \"resolved\": \"https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz\",\n      \"integrity\": \"sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/is-core-module\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/is-core-module/-/is-core-module-2.1.0.tgz\",\n      \"integrity\": \"sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"has\": \"^1.0.3\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/is-date-object\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz\",\n      \"integrity\": \"sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/is-extglob\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz\",\n      \"integrity\": \"sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/is-fullwidth-code-point\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz\",\n      \"integrity\": \"sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/is-glob\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz\",\n      \"integrity\": \"sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"is-extglob\": \"^2.1.1\"\n      },\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/is-negative-zero\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz\",\n      \"integrity\": \"sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      }\n    },\n    \"node_modules/is-number\": {\n      \"version\": \"7.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz\",\n      \"integrity\": \"sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.12.0\"\n      }\n    },\n    \"node_modules/is-regex\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz\",\n      \"integrity\": \"sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"has-symbols\": \"^1.0.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/is-stream\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz\",\n      \"integrity\": \"sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/is-string\": {\n      \"version\": \"1.0.5\",\n      \"resolved\": \"https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz\",\n      \"integrity\": \"sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/is-symbol\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz\",\n      \"integrity\": \"sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"has-symbols\": \"^1.0.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/isarray\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz\",\n      \"integrity\": \"sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=\",\n      \"dev\": true\n    },\n    \"node_modules/isexe\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz\",\n      \"integrity\": \"sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=\",\n      \"dev\": true\n    },\n    \"node_modules/jest-worker\": {\n      \"version\": \"26.6.2\",\n      \"resolved\": \"https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz\",\n      \"integrity\": \"sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@types/node\": \"*\",\n        \"merge-stream\": \"^2.0.0\",\n        \"supports-color\": \"^7.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">= 10.13.0\"\n      }\n    },\n    \"node_modules/jest-worker/node_modules/has-flag\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz\",\n      \"integrity\": \"sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/jest-worker/node_modules/supports-color\": {\n      \"version\": \"7.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz\",\n      \"integrity\": \"sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"has-flag\": \"^4.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/jquery\": {\n      \"version\": \"3.5.1\",\n      \"resolved\": \"https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz\",\n      \"integrity\": \"sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==\"\n    },\n    \"node_modules/jquery-migrate\": {\n      \"version\": \"3.3.2\",\n      \"resolved\": \"https://registry.npmjs.org/jquery-migrate/-/jquery-migrate-3.3.2.tgz\",\n      \"integrity\": \"sha512-L3gYhr7yEtLUSAeqXSicVa0vRD4aGwjw/bWY8YzrO2o/qDY1BaMyP3oB3bZf5Auy3Hu9ynliio0CTyDWCBPVDw==\",\n      \"peerDependencies\": {\n        \"jquery\": \">=3 <4\"\n      }\n    },\n    \"node_modules/js-tokens\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz\",\n      \"integrity\": \"sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==\",\n      \"dev\": true\n    },\n    \"node_modules/js-yaml\": {\n      \"version\": \"3.14.0\",\n      \"resolved\": \"https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz\",\n      \"integrity\": \"sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"argparse\": \"^1.0.7\",\n        \"esprima\": \"^4.0.0\"\n      },\n      \"bin\": {\n        \"js-yaml\": \"bin/js-yaml.js\"\n      }\n    },\n    \"node_modules/jsdoctypeparser\": {\n      \"version\": \"9.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-9.0.0.tgz\",\n      \"integrity\": \"sha512-jrTA2jJIL6/DAEILBEh2/w9QxCuwmvNXIry39Ay/HVfhE3o2yVV0U44blYkqdHA/OKloJEqvJy0xU+GSdE2SIw==\",\n      \"dev\": true,\n      \"bin\": {\n        \"jsdoctypeparser\": \"bin/jsdoctypeparser\"\n      },\n      \"engines\": {\n        \"node\": \">=10\"\n      }\n    },\n    \"node_modules/jsesc\": {\n      \"version\": \"2.5.2\",\n      \"resolved\": \"https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz\",\n      \"integrity\": \"sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==\",\n      \"dev\": true,\n      \"bin\": {\n        \"jsesc\": \"bin/jsesc\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/json-parse-better-errors\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz\",\n      \"integrity\": \"sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==\",\n      \"dev\": true\n    },\n    \"node_modules/json-parse-even-better-errors\": {\n      \"version\": \"2.3.1\",\n      \"resolved\": \"https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz\",\n      \"integrity\": \"sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==\",\n      \"dev\": true\n    },\n    \"node_modules/json-schema-traverse\": {\n      \"version\": \"0.4.1\",\n      \"resolved\": \"https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz\",\n      \"integrity\": \"sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==\",\n      \"dev\": true\n    },\n    \"node_modules/json-stable-stringify-without-jsonify\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz\",\n      \"integrity\": \"sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=\",\n      \"dev\": true\n    },\n    \"node_modules/json5\": {\n      \"version\": \"2.2.3\",\n      \"resolved\": \"https://registry.npmjs.org/json5/-/json5-2.2.3.tgz\",\n      \"integrity\": \"sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==\",\n      \"dev\": true,\n      \"bin\": {\n        \"json5\": \"lib/cli.js\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/klona\": {\n      \"version\": \"2.0.6\",\n      \"resolved\": \"https://registry.npmjs.org/klona/-/klona-2.0.6.tgz\",\n      \"integrity\": \"sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">= 8\"\n      }\n    },\n    \"node_modules/leven\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/leven/-/leven-3.1.0.tgz\",\n      \"integrity\": \"sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/levn\": {\n      \"version\": \"0.4.1\",\n      \"resolved\": \"https://registry.npmjs.org/levn/-/levn-0.4.1.tgz\",\n      \"integrity\": \"sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"prelude-ls\": \"^1.2.1\",\n        \"type-check\": \"~0.4.0\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.8.0\"\n      }\n    },\n    \"node_modules/lines-and-columns\": {\n      \"version\": \"1.2.4\",\n      \"resolved\": \"https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz\",\n      \"integrity\": \"sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==\",\n      \"dev\": true\n    },\n    \"node_modules/load-json-file\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz\",\n      \"integrity\": \"sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"graceful-fs\": \"^4.1.2\",\n        \"parse-json\": \"^2.2.0\",\n        \"pify\": \"^2.0.0\",\n        \"strip-bom\": \"^3.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/load-json-file/node_modules/parse-json\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz\",\n      \"integrity\": \"sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"error-ex\": \"^1.2.0\"\n      },\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/load-json-file/node_modules/pify\": {\n      \"version\": \"2.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/pify/-/pify-2.3.0.tgz\",\n      \"integrity\": \"sha1-7RQaasBDqEnqWISY59yosVMw6Qw=\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/loader-runner\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/loader-runner/-/loader-runner-4.1.0.tgz\",\n      \"integrity\": \"sha512-oR4lB4WvwFoC70ocraKhn5nkKSs23t57h9udUgw8o0iH8hMXeEoRuUgfcvgUwAJ1ZpRqBvcou4N2SMvM1DwMrA==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6.11.5\"\n      }\n    },\n    \"node_modules/loader-utils\": {\n      \"version\": \"1.4.2\",\n      \"resolved\": \"https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz\",\n      \"integrity\": \"sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"big.js\": \"^5.2.2\",\n        \"emojis-list\": \"^3.0.0\",\n        \"json5\": \"^1.0.1\"\n      },\n      \"engines\": {\n        \"node\": \">=4.0.0\"\n      }\n    },\n    \"node_modules/loader-utils/node_modules/json5\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/json5/-/json5-1.0.2.tgz\",\n      \"integrity\": \"sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"minimist\": \"^1.2.0\"\n      },\n      \"bin\": {\n        \"json5\": \"lib/cli.js\"\n      }\n    },\n    \"node_modules/locate-path\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz\",\n      \"integrity\": \"sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"p-locate\": \"^2.0.0\",\n        \"path-exists\": \"^3.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/lodash\": {\n      \"version\": \"4.17.21\",\n      \"resolved\": \"https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz\",\n      \"integrity\": \"sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==\",\n      \"dev\": true\n    },\n    \"node_modules/lower-case\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz\",\n      \"integrity\": \"sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"tslib\": \"^1.10.0\"\n      }\n    },\n    \"node_modules/lru-cache\": {\n      \"version\": \"6.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz\",\n      \"integrity\": \"sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"yallist\": \"^4.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=10\"\n      }\n    },\n    \"node_modules/make-dir\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz\",\n      \"integrity\": \"sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"pify\": \"^4.0.1\",\n        \"semver\": \"^5.6.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/make-error\": {\n      \"version\": \"1.3.6\",\n      \"resolved\": \"https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz\",\n      \"integrity\": \"sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"node_modules/merge-stream\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz\",\n      \"integrity\": \"sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==\",\n      \"dev\": true\n    },\n    \"node_modules/mime-db\": {\n      \"version\": \"1.44.0\",\n      \"resolved\": \"https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz\",\n      \"integrity\": \"sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">= 0.6\"\n      }\n    },\n    \"node_modules/mime-types\": {\n      \"version\": \"2.1.27\",\n      \"resolved\": \"https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz\",\n      \"integrity\": \"sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"mime-db\": \"1.44.0\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.6\"\n      }\n    },\n    \"node_modules/mimic-fn\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz\",\n      \"integrity\": \"sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/mini-css-extract-plugin\": {\n      \"version\": \"1.3.1\",\n      \"resolved\": \"https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.1.tgz\",\n      \"integrity\": \"sha512-jIOheqh9EU98rqj6ZaFTYNNDSFqdakNqaUZfkYwaXPjI9batmXVXX+K71NrqRAgtoGefELBMld1EQ7dqSAD5SQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"loader-utils\": \"^2.0.0\",\n        \"schema-utils\": \"^3.0.0\",\n        \"webpack-sources\": \"^1.1.0\"\n      },\n      \"engines\": {\n        \"node\": \">= 10.13.0\"\n      },\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/webpack\"\n      },\n      \"peerDependencies\": {\n        \"webpack\": \"^4.4.0 || ^5.0.0\"\n      }\n    },\n    \"node_modules/mini-css-extract-plugin/node_modules/loader-utils\": {\n      \"version\": \"2.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz\",\n      \"integrity\": \"sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"big.js\": \"^5.2.2\",\n        \"emojis-list\": \"^3.0.0\",\n        \"json5\": \"^2.1.2\"\n      },\n      \"engines\": {\n        \"node\": \">=8.9.0\"\n      }\n    },\n    \"node_modules/mini-css-extract-plugin/node_modules/schema-utils\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz\",\n      \"integrity\": \"sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@types/json-schema\": \"^7.0.6\",\n        \"ajv\": \"^6.12.5\",\n        \"ajv-keywords\": \"^3.5.2\"\n      },\n      \"engines\": {\n        \"node\": \">= 10.13.0\"\n      },\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/webpack\"\n      }\n    },\n    \"node_modules/minimatch\": {\n      \"version\": \"3.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz\",\n      \"integrity\": \"sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"brace-expansion\": \"^1.1.7\"\n      },\n      \"engines\": {\n        \"node\": \"*\"\n      }\n    },\n    \"node_modules/minimist\": {\n      \"version\": \"1.2.6\",\n      \"resolved\": \"https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz\",\n      \"integrity\": \"sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==\",\n      \"dev\": true\n    },\n    \"node_modules/mkdirp\": {\n      \"version\": \"0.5.5\",\n      \"resolved\": \"https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz\",\n      \"integrity\": \"sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"minimist\": \"^1.2.5\"\n      },\n      \"bin\": {\n        \"mkdirp\": \"bin/cmd.js\"\n      }\n    },\n    \"node_modules/ms\": {\n      \"version\": \"2.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/ms/-/ms-2.1.2.tgz\",\n      \"integrity\": \"sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==\",\n      \"dev\": true\n    },\n    \"node_modules/nanoid\": {\n      \"version\": \"3.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz\",\n      \"integrity\": \"sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==\",\n      \"dev\": true,\n      \"bin\": {\n        \"nanoid\": \"bin/nanoid.cjs\"\n      },\n      \"engines\": {\n        \"node\": \"^10 || ^12 || ^13.7 || ^14 || >=15.0.1\"\n      }\n    },\n    \"node_modules/natural-compare\": {\n      \"version\": \"1.4.0\",\n      \"resolved\": \"https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz\",\n      \"integrity\": \"sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=\",\n      \"dev\": true\n    },\n    \"node_modules/neo-async\": {\n      \"version\": \"2.6.2\",\n      \"resolved\": \"https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz\",\n      \"integrity\": \"sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==\",\n      \"dev\": true\n    },\n    \"node_modules/no-case\": {\n      \"version\": \"3.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/no-case/-/no-case-3.0.3.tgz\",\n      \"integrity\": \"sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"lower-case\": \"^2.0.1\",\n        \"tslib\": \"^1.10.0\"\n      }\n    },\n    \"node_modules/node-releases\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz\",\n      \"integrity\": \"sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==\",\n      \"dev\": true\n    },\n    \"node_modules/normalize-package-data\": {\n      \"version\": \"2.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz\",\n      \"integrity\": \"sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"hosted-git-info\": \"^2.1.4\",\n        \"resolve\": \"^1.10.0\",\n        \"semver\": \"2 || 3 || 4 || 5\",\n        \"validate-npm-package-license\": \"^3.0.1\"\n      }\n    },\n    \"node_modules/normalize-path\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz\",\n      \"integrity\": \"sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/normalize-range\": {\n      \"version\": \"0.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz\",\n      \"integrity\": \"sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/npm-run-path\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz\",\n      \"integrity\": \"sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"path-key\": \"^3.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/nth-check\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz\",\n      \"integrity\": \"sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"boolbase\": \"^1.0.0\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/fb55/nth-check?sponsor=1\"\n      }\n    },\n    \"node_modules/num2fraction\": {\n      \"version\": \"1.2.2\",\n      \"resolved\": \"https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz\",\n      \"integrity\": \"sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=\",\n      \"dev\": true\n    },\n    \"node_modules/object-inspect\": {\n      \"version\": \"1.8.0\",\n      \"resolved\": \"https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz\",\n      \"integrity\": \"sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==\",\n      \"dev\": true,\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/object-keys\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz\",\n      \"integrity\": \"sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      }\n    },\n    \"node_modules/object.assign\": {\n      \"version\": \"4.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz\",\n      \"integrity\": \"sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"call-bind\": \"^1.0.0\",\n        \"define-properties\": \"^1.1.3\",\n        \"has-symbols\": \"^1.0.1\",\n        \"object-keys\": \"^1.1.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/object.getownpropertydescriptors\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz\",\n      \"integrity\": \"sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"define-properties\": \"^1.1.3\",\n        \"es-abstract\": \"^1.17.0-next.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.8\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/object.getownpropertydescriptors/node_modules/es-abstract\": {\n      \"version\": \"1.17.7\",\n      \"resolved\": \"https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz\",\n      \"integrity\": \"sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"es-to-primitive\": \"^1.2.1\",\n        \"function-bind\": \"^1.1.1\",\n        \"has\": \"^1.0.3\",\n        \"has-symbols\": \"^1.0.1\",\n        \"is-callable\": \"^1.2.2\",\n        \"is-regex\": \"^1.1.1\",\n        \"object-inspect\": \"^1.8.0\",\n        \"object-keys\": \"^1.1.1\",\n        \"object.assign\": \"^4.1.1\",\n        \"string.prototype.trimend\": \"^1.0.1\",\n        \"string.prototype.trimstart\": \"^1.0.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/object.values\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz\",\n      \"integrity\": \"sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"define-properties\": \"^1.1.3\",\n        \"es-abstract\": \"^1.17.0-next.1\",\n        \"function-bind\": \"^1.1.1\",\n        \"has\": \"^1.0.3\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/object.values/node_modules/es-abstract\": {\n      \"version\": \"1.17.7\",\n      \"resolved\": \"https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz\",\n      \"integrity\": \"sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"es-to-primitive\": \"^1.2.1\",\n        \"function-bind\": \"^1.1.1\",\n        \"has\": \"^1.0.3\",\n        \"has-symbols\": \"^1.0.1\",\n        \"is-callable\": \"^1.2.2\",\n        \"is-regex\": \"^1.1.1\",\n        \"object-inspect\": \"^1.8.0\",\n        \"object-keys\": \"^1.1.1\",\n        \"object.assign\": \"^4.1.1\",\n        \"string.prototype.trimend\": \"^1.0.1\",\n        \"string.prototype.trimstart\": \"^1.0.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/once\": {\n      \"version\": \"1.4.0\",\n      \"resolved\": \"https://registry.npmjs.org/once/-/once-1.4.0.tgz\",\n      \"integrity\": \"sha1-WDsap3WWHUsROsF9nFC6753Xa9E=\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"wrappy\": \"1\"\n      }\n    },\n    \"node_modules/onetime\": {\n      \"version\": \"5.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz\",\n      \"integrity\": \"sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"mimic-fn\": \"^2.1.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/sindresorhus\"\n      }\n    },\n    \"node_modules/optionator\": {\n      \"version\": \"0.9.1\",\n      \"resolved\": \"https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz\",\n      \"integrity\": \"sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"deep-is\": \"^0.1.3\",\n        \"fast-levenshtein\": \"^2.0.6\",\n        \"levn\": \"^0.4.1\",\n        \"prelude-ls\": \"^1.2.1\",\n        \"type-check\": \"^0.4.0\",\n        \"word-wrap\": \"^1.2.3\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.8.0\"\n      }\n    },\n    \"node_modules/p-limit\": {\n      \"version\": \"1.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz\",\n      \"integrity\": \"sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"p-try\": \"^1.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/p-locate\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz\",\n      \"integrity\": \"sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"p-limit\": \"^1.1.0\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/p-try\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz\",\n      \"integrity\": \"sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/param-case\": {\n      \"version\": \"3.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/param-case/-/param-case-3.0.3.tgz\",\n      \"integrity\": \"sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"dot-case\": \"^3.0.3\",\n        \"tslib\": \"^1.10.0\"\n      }\n    },\n    \"node_modules/parent-module\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz\",\n      \"integrity\": \"sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"callsites\": \"^3.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/parse-json\": {\n      \"version\": \"5.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz\",\n      \"integrity\": \"sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/code-frame\": \"^7.0.0\",\n        \"error-ex\": \"^1.3.1\",\n        \"json-parse-even-better-errors\": \"^2.3.0\",\n        \"lines-and-columns\": \"^1.1.6\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/sindresorhus\"\n      }\n    },\n    \"node_modules/pascal-case\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.1.tgz\",\n      \"integrity\": \"sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"no-case\": \"^3.0.3\",\n        \"tslib\": \"^1.10.0\"\n      }\n    },\n    \"node_modules/path-exists\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz\",\n      \"integrity\": \"sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/path-is-absolute\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz\",\n      \"integrity\": \"sha1-F0uSaHNVNP+8es5r9TpanhtcX18=\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/path-key\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz\",\n      \"integrity\": \"sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/path-parse\": {\n      \"version\": \"1.0.7\",\n      \"resolved\": \"https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz\",\n      \"integrity\": \"sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==\",\n      \"dev\": true\n    },\n    \"node_modules/path-type\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz\",\n      \"integrity\": \"sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/picocolors\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz\",\n      \"integrity\": \"sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==\",\n      \"dev\": true\n    },\n    \"node_modules/picomatch\": {\n      \"version\": \"2.2.2\",\n      \"resolved\": \"https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz\",\n      \"integrity\": \"sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=8.6\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/jonschlinkert\"\n      }\n    },\n    \"node_modules/pify\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/pify/-/pify-4.0.1.tgz\",\n      \"integrity\": \"sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/pkg-dir\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz\",\n      \"integrity\": \"sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"find-up\": \"^2.1.0\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/popper.js\": {\n      \"version\": \"1.16.1\",\n      \"resolved\": \"https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz\",\n      \"integrity\": \"sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==\",\n      \"deprecated\": \"You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1\",\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/popperjs\"\n      }\n    },\n    \"node_modules/postcss\": {\n      \"version\": \"8.2.15\",\n      \"resolved\": \"https://registry.npmjs.org/postcss/-/postcss-8.2.15.tgz\",\n      \"integrity\": \"sha512-2zO3b26eJD/8rb106Qu2o7Qgg52ND5HPjcyQiK2B98O388h43A448LCslC0dI2P97wCAQRJsFvwTRcXxTKds+Q==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"colorette\": \"^1.2.2\",\n        \"nanoid\": \"^3.1.23\",\n        \"source-map\": \"^0.6.1\"\n      },\n      \"engines\": {\n        \"node\": \"^10 || ^12 || >=14\"\n      },\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/postcss/\"\n      }\n    },\n    \"node_modules/postcss-loader\": {\n      \"version\": \"7.2.4\",\n      \"resolved\": \"https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.2.4.tgz\",\n      \"integrity\": \"sha512-F88rpxxNspo5hatIc+orYwZDtHFaVFOSIVAx+fBfJC1GmhWbVmPWtmg2gXKE1OxJbneOSGn8PWdIwsZFcruS+w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"cosmiconfig\": \"^8.1.3\",\n        \"cosmiconfig-typescript-loader\": \"^4.3.0\",\n        \"klona\": \"^2.0.6\",\n        \"semver\": \"^7.3.8\"\n      },\n      \"engines\": {\n        \"node\": \">= 14.15.0\"\n      },\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/webpack\"\n      },\n      \"peerDependencies\": {\n        \"postcss\": \"^7.0.0 || ^8.0.1\",\n        \"ts-node\": \">=10\",\n        \"typescript\": \">=4\",\n        \"webpack\": \"^5.0.0\"\n      },\n      \"peerDependenciesMeta\": {\n        \"ts-node\": {\n          \"optional\": true\n        },\n        \"typescript\": {\n          \"optional\": true\n        }\n      }\n    },\n    \"node_modules/postcss-loader/node_modules/semver\": {\n      \"version\": \"7.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/semver/-/semver-7.5.0.tgz\",\n      \"integrity\": \"sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"lru-cache\": \"^6.0.0\"\n      },\n      \"bin\": {\n        \"semver\": \"bin/semver.js\"\n      },\n      \"engines\": {\n        \"node\": \">=10\"\n      }\n    },\n    \"node_modules/postcss-modules-extract-imports\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz\",\n      \"integrity\": \"sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \"^10 || ^12 || >= 14\"\n      },\n      \"peerDependencies\": {\n        \"postcss\": \"^8.1.0\"\n      }\n    },\n    \"node_modules/postcss-modules-local-by-default\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz\",\n      \"integrity\": \"sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"icss-utils\": \"^5.0.0\",\n        \"postcss-selector-parser\": \"^6.0.2\",\n        \"postcss-value-parser\": \"^4.1.0\"\n      },\n      \"engines\": {\n        \"node\": \"^10 || ^12 || >= 14\"\n      },\n      \"peerDependencies\": {\n        \"postcss\": \"^8.1.0\"\n      }\n    },\n    \"node_modules/postcss-modules-scope\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz\",\n      \"integrity\": \"sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"postcss-selector-parser\": \"^6.0.4\"\n      },\n      \"engines\": {\n        \"node\": \"^10 || ^12 || >= 14\"\n      },\n      \"peerDependencies\": {\n        \"postcss\": \"^8.1.0\"\n      }\n    },\n    \"node_modules/postcss-modules-values\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz\",\n      \"integrity\": \"sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"icss-utils\": \"^5.0.0\"\n      },\n      \"engines\": {\n        \"node\": \"^10 || ^12 || >= 14\"\n      },\n      \"peerDependencies\": {\n        \"postcss\": \"^8.1.0\"\n      }\n    },\n    \"node_modules/postcss-selector-parser\": {\n      \"version\": \"6.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz\",\n      \"integrity\": \"sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"cssesc\": \"^3.0.0\",\n        \"indexes-of\": \"^1.0.1\",\n        \"uniq\": \"^1.0.1\",\n        \"util-deprecate\": \"^1.0.2\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/postcss-value-parser\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz\",\n      \"integrity\": \"sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==\",\n      \"dev\": true\n    },\n    \"node_modules/postcss/node_modules/source-map\": {\n      \"version\": \"0.6.1\",\n      \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\",\n      \"integrity\": \"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/prelude-ls\": {\n      \"version\": \"1.2.1\",\n      \"resolved\": \"https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz\",\n      \"integrity\": \"sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">= 0.8.0\"\n      }\n    },\n    \"node_modules/pretty-error\": {\n      \"version\": \"2.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz\",\n      \"integrity\": \"sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"lodash\": \"^4.17.20\",\n        \"renderkid\": \"^2.0.4\"\n      }\n    },\n    \"node_modules/progress\": {\n      \"version\": \"2.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/progress/-/progress-2.0.3.tgz\",\n      \"integrity\": \"sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.4.0\"\n      }\n    },\n    \"node_modules/pump\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/pump/-/pump-3.0.0.tgz\",\n      \"integrity\": \"sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"end-of-stream\": \"^1.1.0\",\n        \"once\": \"^1.3.1\"\n      }\n    },\n    \"node_modules/punycode\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz\",\n      \"integrity\": \"sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/randombytes\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz\",\n      \"integrity\": \"sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"safe-buffer\": \"^5.1.0\"\n      }\n    },\n    \"node_modules/read-pkg\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz\",\n      \"integrity\": \"sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"load-json-file\": \"^2.0.0\",\n        \"normalize-package-data\": \"^2.3.2\",\n        \"path-type\": \"^2.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/read-pkg-up\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz\",\n      \"integrity\": \"sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"find-up\": \"^2.0.0\",\n        \"read-pkg\": \"^2.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/read-pkg/node_modules/path-type\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz\",\n      \"integrity\": \"sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"pify\": \"^2.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/read-pkg/node_modules/pify\": {\n      \"version\": \"2.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/pify/-/pify-2.3.0.tgz\",\n      \"integrity\": \"sha1-7RQaasBDqEnqWISY59yosVMw6Qw=\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/readdirp\": {\n      \"version\": \"3.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz\",\n      \"integrity\": \"sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"picomatch\": \"^2.2.1\"\n      },\n      \"engines\": {\n        \"node\": \">=8.10.0\"\n      }\n    },\n    \"node_modules/rechoir\": {\n      \"version\": \"0.7.0\",\n      \"resolved\": \"https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz\",\n      \"integrity\": \"sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"resolve\": \"^1.9.0\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.10\"\n      }\n    },\n    \"node_modules/reduce-flatten\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz\",\n      \"integrity\": \"sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/regenerate\": {\n      \"version\": \"1.4.2\",\n      \"resolved\": \"https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz\",\n      \"integrity\": \"sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==\",\n      \"dev\": true\n    },\n    \"node_modules/regenerate-unicode-properties\": {\n      \"version\": \"8.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz\",\n      \"integrity\": \"sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"regenerate\": \"^1.4.0\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/regenerator-runtime\": {\n      \"version\": \"0.13.7\",\n      \"resolved\": \"https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz\",\n      \"integrity\": \"sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==\",\n      \"dev\": true\n    },\n    \"node_modules/regenerator-transform\": {\n      \"version\": \"0.14.5\",\n      \"resolved\": \"https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz\",\n      \"integrity\": \"sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@babel/runtime\": \"^7.8.4\"\n      }\n    },\n    \"node_modules/regexpp\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz\",\n      \"integrity\": \"sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=8\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/mysticatea\"\n      }\n    },\n    \"node_modules/regexpu-core\": {\n      \"version\": \"4.7.1\",\n      \"resolved\": \"https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz\",\n      \"integrity\": \"sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"regenerate\": \"^1.4.0\",\n        \"regenerate-unicode-properties\": \"^8.2.0\",\n        \"regjsgen\": \"^0.5.1\",\n        \"regjsparser\": \"^0.6.4\",\n        \"unicode-match-property-ecmascript\": \"^1.0.4\",\n        \"unicode-match-property-value-ecmascript\": \"^1.2.0\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/regextras\": {\n      \"version\": \"0.7.1\",\n      \"resolved\": \"https://registry.npmjs.org/regextras/-/regextras-0.7.1.tgz\",\n      \"integrity\": \"sha512-9YXf6xtW+qzQ+hcMQXx95MOvfqXFgsKDZodX3qZB0x2n5Z94ioetIITsBtvJbiOyxa/6s9AtyweBLCdPmPko/w==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.1.14\"\n      }\n    },\n    \"node_modules/regjsgen\": {\n      \"version\": \"0.5.2\",\n      \"resolved\": \"https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz\",\n      \"integrity\": \"sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==\",\n      \"dev\": true\n    },\n    \"node_modules/regjsparser\": {\n      \"version\": \"0.6.4\",\n      \"resolved\": \"https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz\",\n      \"integrity\": \"sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"jsesc\": \"~0.5.0\"\n      },\n      \"bin\": {\n        \"regjsparser\": \"bin/parser\"\n      }\n    },\n    \"node_modules/regjsparser/node_modules/jsesc\": {\n      \"version\": \"0.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz\",\n      \"integrity\": \"sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=\",\n      \"dev\": true,\n      \"bin\": {\n        \"jsesc\": \"bin/jsesc\"\n      }\n    },\n    \"node_modules/relateurl\": {\n      \"version\": \"0.2.7\",\n      \"resolved\": \"https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz\",\n      \"integrity\": \"sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">= 0.10\"\n      }\n    },\n    \"node_modules/renderkid\": {\n      \"version\": \"2.0.7\",\n      \"resolved\": \"https://registry.npmjs.org/renderkid/-/renderkid-2.0.7.tgz\",\n      \"integrity\": \"sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"css-select\": \"^4.1.3\",\n        \"dom-converter\": \"^0.2.0\",\n        \"htmlparser2\": \"^6.1.0\",\n        \"lodash\": \"^4.17.21\",\n        \"strip-ansi\": \"^3.0.1\"\n      }\n    },\n    \"node_modules/renderkid/node_modules/ansi-regex\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz\",\n      \"integrity\": \"sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/renderkid/node_modules/strip-ansi\": {\n      \"version\": \"3.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz\",\n      \"integrity\": \"sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"ansi-regex\": \"^2.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/resolve\": {\n      \"version\": \"1.19.0\",\n      \"resolved\": \"https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz\",\n      \"integrity\": \"sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"is-core-module\": \"^2.1.0\",\n        \"path-parse\": \"^1.0.6\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/resolve-cwd\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz\",\n      \"integrity\": \"sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"resolve-from\": \"^5.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/resolve-cwd/node_modules/resolve-from\": {\n      \"version\": \"5.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz\",\n      \"integrity\": \"sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/resolve-from\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz\",\n      \"integrity\": \"sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/rimraf\": {\n      \"version\": \"2.6.3\",\n      \"resolved\": \"https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz\",\n      \"integrity\": \"sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"glob\": \"^7.1.3\"\n      },\n      \"bin\": {\n        \"rimraf\": \"bin.js\"\n      }\n    },\n    \"node_modules/safe-buffer\": {\n      \"version\": \"5.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz\",\n      \"integrity\": \"sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==\",\n      \"dev\": true\n    },\n    \"node_modules/sass\": {\n      \"version\": \"1.29.0\",\n      \"resolved\": \"https://registry.npmjs.org/sass/-/sass-1.29.0.tgz\",\n      \"integrity\": \"sha512-ZpwAUFgnvAUCdkjwPREny+17BpUj8nh5Yr6zKPGtLNTLrmtoRYIjm7njP24COhjJldjwW1dcv52Lpf4tNZVVRA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"chokidar\": \">=2.0.0 <4.0.0\"\n      },\n      \"bin\": {\n        \"sass\": \"sass.js\"\n      },\n      \"engines\": {\n        \"node\": \">=8.9.0\"\n      }\n    },\n    \"node_modules/sass-loader\": {\n      \"version\": \"10.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/sass-loader/-/sass-loader-10.1.0.tgz\",\n      \"integrity\": \"sha512-ZCKAlczLBbFd3aGAhowpYEy69Te3Z68cg8bnHHl6WnSCvnKpbM6pQrz957HWMa8LKVuhnD9uMplmMAHwGQtHeg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"klona\": \"^2.0.4\",\n        \"loader-utils\": \"^2.0.0\",\n        \"neo-async\": \"^2.6.2\",\n        \"schema-utils\": \"^3.0.0\",\n        \"semver\": \"^7.3.2\"\n      },\n      \"engines\": {\n        \"node\": \">= 10.13.0\"\n      },\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/webpack\"\n      },\n      \"peerDependencies\": {\n        \"fibers\": \">= 3.1.0\",\n        \"node-sass\": \"^4.0.0 || ^5.0.0\",\n        \"sass\": \"^1.3.0\",\n        \"webpack\": \"^4.36.0 || ^5.0.0\"\n      },\n      \"peerDependenciesMeta\": {\n        \"fibers\": {\n          \"optional\": true\n        },\n        \"node-sass\": {\n          \"optional\": true\n        },\n        \"sass\": {\n          \"optional\": true\n        }\n      }\n    },\n    \"node_modules/sass-loader/node_modules/loader-utils\": {\n      \"version\": \"2.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz\",\n      \"integrity\": \"sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"big.js\": \"^5.2.2\",\n        \"emojis-list\": \"^3.0.0\",\n        \"json5\": \"^2.1.2\"\n      },\n      \"engines\": {\n        \"node\": \">=8.9.0\"\n      }\n    },\n    \"node_modules/sass-loader/node_modules/schema-utils\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz\",\n      \"integrity\": \"sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@types/json-schema\": \"^7.0.6\",\n        \"ajv\": \"^6.12.5\",\n        \"ajv-keywords\": \"^3.5.2\"\n      },\n      \"engines\": {\n        \"node\": \">= 10.13.0\"\n      },\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/webpack\"\n      }\n    },\n    \"node_modules/sass-loader/node_modules/semver\": {\n      \"version\": \"7.3.2\",\n      \"resolved\": \"https://registry.npmjs.org/semver/-/semver-7.3.2.tgz\",\n      \"integrity\": \"sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==\",\n      \"dev\": true,\n      \"bin\": {\n        \"semver\": \"bin/semver.js\"\n      },\n      \"engines\": {\n        \"node\": \">=10\"\n      }\n    },\n    \"node_modules/schema-utils\": {\n      \"version\": \"2.7.1\",\n      \"resolved\": \"https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz\",\n      \"integrity\": \"sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@types/json-schema\": \"^7.0.5\",\n        \"ajv\": \"^6.12.4\",\n        \"ajv-keywords\": \"^3.5.2\"\n      },\n      \"engines\": {\n        \"node\": \">= 8.9.0\"\n      },\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/webpack\"\n      }\n    },\n    \"node_modules/semver\": {\n      \"version\": \"5.7.1\",\n      \"resolved\": \"https://registry.npmjs.org/semver/-/semver-5.7.1.tgz\",\n      \"integrity\": \"sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==\",\n      \"dev\": true,\n      \"bin\": {\n        \"semver\": \"bin/semver\"\n      }\n    },\n    \"node_modules/serialize-javascript\": {\n      \"version\": \"5.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz\",\n      \"integrity\": \"sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"randombytes\": \"^2.1.0\"\n      }\n    },\n    \"node_modules/shebang-command\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz\",\n      \"integrity\": \"sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"shebang-regex\": \"^3.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/shebang-regex\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz\",\n      \"integrity\": \"sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/signal-exit\": {\n      \"version\": \"3.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz\",\n      \"integrity\": \"sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==\",\n      \"dev\": true\n    },\n    \"node_modules/slice-ansi\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz\",\n      \"integrity\": \"sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"ansi-styles\": \"^3.2.0\",\n        \"astral-regex\": \"^1.0.0\",\n        \"is-fullwidth-code-point\": \"^2.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/source-list-map\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz\",\n      \"integrity\": \"sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==\",\n      \"dev\": true\n    },\n    \"node_modules/source-map\": {\n      \"version\": \"0.5.7\",\n      \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz\",\n      \"integrity\": \"sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/source-map-support\": {\n      \"version\": \"0.5.21\",\n      \"resolved\": \"https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz\",\n      \"integrity\": \"sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"buffer-from\": \"^1.0.0\",\n        \"source-map\": \"^0.6.0\"\n      }\n    },\n    \"node_modules/source-map-support/node_modules/source-map\": {\n      \"version\": \"0.6.1\",\n      \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\",\n      \"integrity\": \"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/spdx-correct\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz\",\n      \"integrity\": \"sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"spdx-expression-parse\": \"^3.0.0\",\n        \"spdx-license-ids\": \"^3.0.0\"\n      }\n    },\n    \"node_modules/spdx-exceptions\": {\n      \"version\": \"2.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz\",\n      \"integrity\": \"sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==\",\n      \"dev\": true\n    },\n    \"node_modules/spdx-expression-parse\": {\n      \"version\": \"3.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz\",\n      \"integrity\": \"sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"spdx-exceptions\": \"^2.1.0\",\n        \"spdx-license-ids\": \"^3.0.0\"\n      }\n    },\n    \"node_modules/spdx-license-ids\": {\n      \"version\": \"3.0.6\",\n      \"resolved\": \"https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz\",\n      \"integrity\": \"sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==\",\n      \"dev\": true\n    },\n    \"node_modules/sprintf-js\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz\",\n      \"integrity\": \"sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=\",\n      \"dev\": true\n    },\n    \"node_modules/string-width\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz\",\n      \"integrity\": \"sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"emoji-regex\": \"^7.0.1\",\n        \"is-fullwidth-code-point\": \"^2.0.0\",\n        \"strip-ansi\": \"^5.1.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/string-width/node_modules/ansi-regex\": {\n      \"version\": \"4.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz\",\n      \"integrity\": \"sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/string-width/node_modules/strip-ansi\": {\n      \"version\": \"5.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz\",\n      \"integrity\": \"sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"ansi-regex\": \"^4.1.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/string.prototype.trimend\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz\",\n      \"integrity\": \"sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"call-bind\": \"^1.0.0\",\n        \"define-properties\": \"^1.1.3\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/string.prototype.trimstart\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz\",\n      \"integrity\": \"sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"call-bind\": \"^1.0.0\",\n        \"define-properties\": \"^1.1.3\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/strip-ansi\": {\n      \"version\": \"6.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz\",\n      \"integrity\": \"sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"ansi-regex\": \"^5.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/strip-bom\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz\",\n      \"integrity\": \"sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/strip-final-newline\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz\",\n      \"integrity\": \"sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/strip-json-comments\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz\",\n      \"integrity\": \"sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=8\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/sindresorhus\"\n      }\n    },\n    \"node_modules/supports-color\": {\n      \"version\": \"5.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz\",\n      \"integrity\": \"sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"has-flag\": \"^3.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/table\": {\n      \"version\": \"5.4.6\",\n      \"resolved\": \"https://registry.npmjs.org/table/-/table-5.4.6.tgz\",\n      \"integrity\": \"sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"ajv\": \"^6.10.2\",\n        \"lodash\": \"^4.17.14\",\n        \"slice-ansi\": \"^2.1.0\",\n        \"string-width\": \"^3.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6.0.0\"\n      }\n    },\n    \"node_modules/table-layout\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/table-layout/-/table-layout-1.0.1.tgz\",\n      \"integrity\": \"sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"array-back\": \"^4.0.1\",\n        \"deep-extend\": \"~0.6.0\",\n        \"typical\": \"^5.2.0\",\n        \"wordwrapjs\": \"^4.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8.0.0\"\n      }\n    },\n    \"node_modules/tapable\": {\n      \"version\": \"1.1.3\",\n      \"resolved\": \"https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz\",\n      \"integrity\": \"sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/terser\": {\n      \"version\": \"4.8.1\",\n      \"resolved\": \"https://registry.npmjs.org/terser/-/terser-4.8.1.tgz\",\n      \"integrity\": \"sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"commander\": \"^2.20.0\",\n        \"source-map\": \"~0.6.1\",\n        \"source-map-support\": \"~0.5.12\"\n      },\n      \"bin\": {\n        \"terser\": \"bin/terser\"\n      },\n      \"engines\": {\n        \"node\": \">=6.0.0\"\n      }\n    },\n    \"node_modules/terser-webpack-plugin\": {\n      \"version\": \"5.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.0.3.tgz\",\n      \"integrity\": \"sha512-zFdGk8Lh9ZJGPxxPE6jwysOlATWB8GMW8HcfGULWA/nPal+3VdATflQvSBSLQJRCmYZnfFJl6vkRTiwJGNgPiQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"jest-worker\": \"^26.6.1\",\n        \"p-limit\": \"^3.0.2\",\n        \"schema-utils\": \"^3.0.0\",\n        \"serialize-javascript\": \"^5.0.1\",\n        \"source-map\": \"^0.6.1\",\n        \"terser\": \"^5.3.8\"\n      },\n      \"engines\": {\n        \"node\": \">= 10.13.0\"\n      },\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/webpack\"\n      },\n      \"peerDependencies\": {\n        \"webpack\": \"^5.1.0\"\n      }\n    },\n    \"node_modules/terser-webpack-plugin/node_modules/acorn\": {\n      \"version\": \"8.7.1\",\n      \"resolved\": \"https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz\",\n      \"integrity\": \"sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==\",\n      \"dev\": true,\n      \"bin\": {\n        \"acorn\": \"bin/acorn\"\n      },\n      \"engines\": {\n        \"node\": \">=0.4.0\"\n      }\n    },\n    \"node_modules/terser-webpack-plugin/node_modules/commander\": {\n      \"version\": \"2.20.3\",\n      \"resolved\": \"https://registry.npmjs.org/commander/-/commander-2.20.3.tgz\",\n      \"integrity\": \"sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==\",\n      \"dev\": true\n    },\n    \"node_modules/terser-webpack-plugin/node_modules/p-limit\": {\n      \"version\": \"3.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz\",\n      \"integrity\": \"sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"p-try\": \"^2.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=10\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/sindresorhus\"\n      }\n    },\n    \"node_modules/terser-webpack-plugin/node_modules/p-try\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz\",\n      \"integrity\": \"sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/terser-webpack-plugin/node_modules/schema-utils\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz\",\n      \"integrity\": \"sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@types/json-schema\": \"^7.0.6\",\n        \"ajv\": \"^6.12.5\",\n        \"ajv-keywords\": \"^3.5.2\"\n      },\n      \"engines\": {\n        \"node\": \">= 10.13.0\"\n      },\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/webpack\"\n      }\n    },\n    \"node_modules/terser-webpack-plugin/node_modules/source-map\": {\n      \"version\": \"0.6.1\",\n      \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\",\n      \"integrity\": \"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/terser-webpack-plugin/node_modules/terser\": {\n      \"version\": \"5.14.2\",\n      \"resolved\": \"https://registry.npmjs.org/terser/-/terser-5.14.2.tgz\",\n      \"integrity\": \"sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@jridgewell/source-map\": \"^0.3.2\",\n        \"acorn\": \"^8.5.0\",\n        \"commander\": \"^2.20.0\",\n        \"source-map-support\": \"~0.5.20\"\n      },\n      \"bin\": {\n        \"terser\": \"bin/terser\"\n      },\n      \"engines\": {\n        \"node\": \">=10\"\n      }\n    },\n    \"node_modules/terser/node_modules/commander\": {\n      \"version\": \"2.20.3\",\n      \"resolved\": \"https://registry.npmjs.org/commander/-/commander-2.20.3.tgz\",\n      \"integrity\": \"sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==\",\n      \"dev\": true\n    },\n    \"node_modules/terser/node_modules/source-map\": {\n      \"version\": \"0.6.1\",\n      \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\",\n      \"integrity\": \"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/text-table\": {\n      \"version\": \"0.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz\",\n      \"integrity\": \"sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=\",\n      \"dev\": true\n    },\n    \"node_modules/to-fast-properties\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz\",\n      \"integrity\": \"sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/to-regex-range\": {\n      \"version\": \"5.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz\",\n      \"integrity\": \"sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"is-number\": \"^7.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8.0\"\n      }\n    },\n    \"node_modules/ts-node\": {\n      \"version\": \"10.9.1\",\n      \"resolved\": \"https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz\",\n      \"integrity\": \"sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==\",\n      \"dev\": true,\n      \"peer\": true,\n      \"dependencies\": {\n        \"@cspotcode/source-map-support\": \"^0.8.0\",\n        \"@tsconfig/node10\": \"^1.0.7\",\n        \"@tsconfig/node12\": \"^1.0.7\",\n        \"@tsconfig/node14\": \"^1.0.0\",\n        \"@tsconfig/node16\": \"^1.0.2\",\n        \"acorn\": \"^8.4.1\",\n        \"acorn-walk\": \"^8.1.1\",\n        \"arg\": \"^4.1.0\",\n        \"create-require\": \"^1.1.0\",\n        \"diff\": \"^4.0.1\",\n        \"make-error\": \"^1.1.1\",\n        \"v8-compile-cache-lib\": \"^3.0.1\",\n        \"yn\": \"3.1.1\"\n      },\n      \"bin\": {\n        \"ts-node\": \"dist/bin.js\",\n        \"ts-node-cwd\": \"dist/bin-cwd.js\",\n        \"ts-node-esm\": \"dist/bin-esm.js\",\n        \"ts-node-script\": \"dist/bin-script.js\",\n        \"ts-node-transpile-only\": \"dist/bin-transpile.js\",\n        \"ts-script\": \"dist/bin-script-deprecated.js\"\n      },\n      \"peerDependencies\": {\n        \"@swc/core\": \">=1.2.50\",\n        \"@swc/wasm\": \">=1.2.50\",\n        \"@types/node\": \"*\",\n        \"typescript\": \">=2.7\"\n      },\n      \"peerDependenciesMeta\": {\n        \"@swc/core\": {\n          \"optional\": true\n        },\n        \"@swc/wasm\": {\n          \"optional\": true\n        }\n      }\n    },\n    \"node_modules/ts-node/node_modules/acorn\": {\n      \"version\": \"8.8.2\",\n      \"resolved\": \"https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz\",\n      \"integrity\": \"sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==\",\n      \"dev\": true,\n      \"peer\": true,\n      \"bin\": {\n        \"acorn\": \"bin/acorn\"\n      },\n      \"engines\": {\n        \"node\": \">=0.4.0\"\n      }\n    },\n    \"node_modules/tsconfig-paths\": {\n      \"version\": \"3.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz\",\n      \"integrity\": \"sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@types/json5\": \"^0.0.29\",\n        \"json5\": \"^1.0.1\",\n        \"minimist\": \"^1.2.0\",\n        \"strip-bom\": \"^3.0.0\"\n      }\n    },\n    \"node_modules/tsconfig-paths/node_modules/json5\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/json5/-/json5-1.0.2.tgz\",\n      \"integrity\": \"sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"minimist\": \"^1.2.0\"\n      },\n      \"bin\": {\n        \"json5\": \"lib/cli.js\"\n      }\n    },\n    \"node_modules/tslib\": {\n      \"version\": \"1.14.1\",\n      \"resolved\": \"https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz\",\n      \"integrity\": \"sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==\",\n      \"dev\": true\n    },\n    \"node_modules/type-check\": {\n      \"version\": \"0.4.0\",\n      \"resolved\": \"https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz\",\n      \"integrity\": \"sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"prelude-ls\": \"^1.2.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.8.0\"\n      }\n    },\n    \"node_modules/type-fest\": {\n      \"version\": \"0.8.1\",\n      \"resolved\": \"https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz\",\n      \"integrity\": \"sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/typescript\": {\n      \"version\": \"5.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz\",\n      \"integrity\": \"sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==\",\n      \"dev\": true,\n      \"peer\": true,\n      \"bin\": {\n        \"tsc\": \"bin/tsc\",\n        \"tsserver\": \"bin/tsserver\"\n      },\n      \"engines\": {\n        \"node\": \">=12.20\"\n      }\n    },\n    \"node_modules/typical\": {\n      \"version\": \"5.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/typical/-/typical-5.2.0.tgz\",\n      \"integrity\": \"sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/unicode-canonical-property-names-ecmascript\": {\n      \"version\": \"1.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz\",\n      \"integrity\": \"sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/unicode-match-property-ecmascript\": {\n      \"version\": \"1.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz\",\n      \"integrity\": \"sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"unicode-canonical-property-names-ecmascript\": \"^1.0.4\",\n        \"unicode-property-aliases-ecmascript\": \"^1.0.4\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/unicode-match-property-value-ecmascript\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz\",\n      \"integrity\": \"sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/unicode-property-aliases-ecmascript\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz\",\n      \"integrity\": \"sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/uniq\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz\",\n      \"integrity\": \"sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=\",\n      \"dev\": true\n    },\n    \"node_modules/uri-js\": {\n      \"version\": \"4.4.0\",\n      \"resolved\": \"https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz\",\n      \"integrity\": \"sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"punycode\": \"^2.1.0\"\n      }\n    },\n    \"node_modules/util-deprecate\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz\",\n      \"integrity\": \"sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=\",\n      \"dev\": true\n    },\n    \"node_modules/util.promisify\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz\",\n      \"integrity\": \"sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"define-properties\": \"^1.1.2\",\n        \"object.getownpropertydescriptors\": \"^2.0.3\"\n      }\n    },\n    \"node_modules/utila\": {\n      \"version\": \"0.4.0\",\n      \"resolved\": \"https://registry.npmjs.org/utila/-/utila-0.4.0.tgz\",\n      \"integrity\": \"sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=\",\n      \"dev\": true\n    },\n    \"node_modules/v8-compile-cache\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz\",\n      \"integrity\": \"sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==\",\n      \"dev\": true\n    },\n    \"node_modules/v8-compile-cache-lib\": {\n      \"version\": \"3.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz\",\n      \"integrity\": \"sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"node_modules/validate-npm-package-license\": {\n      \"version\": \"3.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz\",\n      \"integrity\": \"sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"spdx-correct\": \"^3.0.0\",\n        \"spdx-expression-parse\": \"^3.0.0\"\n      }\n    },\n    \"node_modules/watchpack\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/watchpack/-/watchpack-2.0.1.tgz\",\n      \"integrity\": \"sha512-vO8AKGX22ZRo6PiOFM9dC0re8IcKh8Kd/aH2zeqUc6w4/jBGlTy2P7fTC6ekT0NjVeGjgU2dGC5rNstKkeLEQg==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"glob-to-regexp\": \"^0.4.1\",\n        \"graceful-fs\": \"^4.1.2\"\n      },\n      \"engines\": {\n        \"node\": \">=10.13.0\"\n      }\n    },\n    \"node_modules/webpack\": {\n      \"version\": \"5.6.0\",\n      \"resolved\": \"https://registry.npmjs.org/webpack/-/webpack-5.6.0.tgz\",\n      \"integrity\": \"sha512-SIeFuBhuheKElRbd84O35UhKc0nxlgSwtzm2ksZ0BVhRJqxVJxEguT/pYhfiR0le/pxTa1VsCp7EOYyTsa6XOA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@types/eslint-scope\": \"^3.7.0\",\n        \"@types/estree\": \"^0.0.45\",\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/helper-module-context\": \"1.9.0\",\n        \"@webassemblyjs/wasm-edit\": \"1.9.0\",\n        \"@webassemblyjs/wasm-parser\": \"1.9.0\",\n        \"acorn\": \"^8.0.4\",\n        \"browserslist\": \"^4.14.5\",\n        \"chrome-trace-event\": \"^1.0.2\",\n        \"enhanced-resolve\": \"^5.3.1\",\n        \"eslint-scope\": \"^5.1.1\",\n        \"events\": \"^3.2.0\",\n        \"glob-to-regexp\": \"^0.4.1\",\n        \"graceful-fs\": \"^4.2.4\",\n        \"json-parse-better-errors\": \"^1.0.2\",\n        \"loader-runner\": \"^4.1.0\",\n        \"mime-types\": \"^2.1.27\",\n        \"neo-async\": \"^2.6.2\",\n        \"pkg-dir\": \"^4.2.0\",\n        \"schema-utils\": \"^3.0.0\",\n        \"tapable\": \"^2.0.0\",\n        \"terser-webpack-plugin\": \"^5.0.3\",\n        \"watchpack\": \"^2.0.0\",\n        \"webpack-sources\": \"^2.1.1\"\n      },\n      \"bin\": {\n        \"webpack\": \"bin/webpack.js\"\n      },\n      \"engines\": {\n        \"node\": \">=10.13.0\"\n      },\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/webpack\"\n      },\n      \"peerDependenciesMeta\": {\n        \"webpack-cli\": {\n          \"optional\": true\n        }\n      }\n    },\n    \"node_modules/webpack-cli\": {\n      \"version\": \"4.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.2.0.tgz\",\n      \"integrity\": \"sha512-EIl3k88vaF4fSxWSgtAQR+VwicfLMTZ9amQtqS4o+TDPW9HGaEpbFBbAZ4A3ZOT5SOnMxNOzROsSTPiE8tBJPA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@webpack-cli/info\": \"^1.1.0\",\n        \"@webpack-cli/serve\": \"^1.1.0\",\n        \"colorette\": \"^1.2.1\",\n        \"command-line-usage\": \"^6.1.0\",\n        \"commander\": \"^6.2.0\",\n        \"enquirer\": \"^2.3.6\",\n        \"execa\": \"^4.1.0\",\n        \"import-local\": \"^3.0.2\",\n        \"interpret\": \"^2.2.0\",\n        \"leven\": \"^3.1.0\",\n        \"rechoir\": \"^0.7.0\",\n        \"v8-compile-cache\": \"^2.2.0\",\n        \"webpack-merge\": \"^4.2.2\"\n      },\n      \"bin\": {\n        \"webpack-cli\": \"bin/cli.js\"\n      },\n      \"engines\": {\n        \"node\": \">=10.13.0\"\n      },\n      \"peerDependencies\": {\n        \"webpack\": \"4.x.x || 5.x.x\"\n      },\n      \"peerDependenciesMeta\": {\n        \"@webpack-cli/generate-loader\": {\n          \"optional\": true\n        },\n        \"@webpack-cli/generate-plugin\": {\n          \"optional\": true\n        },\n        \"@webpack-cli/init\": {\n          \"optional\": true\n        },\n        \"@webpack-cli/migrate\": {\n          \"optional\": true\n        },\n        \"webpack-bundle-analyzer\": {\n          \"optional\": true\n        },\n        \"webpack-dev-server\": {\n          \"optional\": true\n        }\n      }\n    },\n    \"node_modules/webpack-cli/node_modules/commander\": {\n      \"version\": \"6.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/commander/-/commander-6.2.0.tgz\",\n      \"integrity\": \"sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">= 6\"\n      }\n    },\n    \"node_modules/webpack-merge\": {\n      \"version\": \"4.2.2\",\n      \"resolved\": \"https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz\",\n      \"integrity\": \"sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"lodash\": \"^4.17.15\"\n      }\n    },\n    \"node_modules/webpack-sources\": {\n      \"version\": \"1.4.3\",\n      \"resolved\": \"https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz\",\n      \"integrity\": \"sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"source-list-map\": \"^2.0.0\",\n        \"source-map\": \"~0.6.1\"\n      }\n    },\n    \"node_modules/webpack-sources/node_modules/source-map\": {\n      \"version\": \"0.6.1\",\n      \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\",\n      \"integrity\": \"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/webpack/node_modules/acorn\": {\n      \"version\": \"8.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/acorn/-/acorn-8.0.4.tgz\",\n      \"integrity\": \"sha512-XNP0PqF1XD19ZlLKvB7cMmnZswW4C/03pRHgirB30uSJTaS3A3V1/P4sS3HPvFmjoriPCJQs+JDSbm4bL1TxGQ==\",\n      \"dev\": true,\n      \"bin\": {\n        \"acorn\": \"bin/acorn\"\n      },\n      \"engines\": {\n        \"node\": \">=0.4.0\"\n      }\n    },\n    \"node_modules/webpack/node_modules/eslint-scope\": {\n      \"version\": \"5.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz\",\n      \"integrity\": \"sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"esrecurse\": \"^4.3.0\",\n        \"estraverse\": \"^4.1.1\"\n      },\n      \"engines\": {\n        \"node\": \">=8.0.0\"\n      }\n    },\n    \"node_modules/webpack/node_modules/find-up\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz\",\n      \"integrity\": \"sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"locate-path\": \"^5.0.0\",\n        \"path-exists\": \"^4.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/webpack/node_modules/locate-path\": {\n      \"version\": \"5.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz\",\n      \"integrity\": \"sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"p-locate\": \"^4.1.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/webpack/node_modules/p-limit\": {\n      \"version\": \"2.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz\",\n      \"integrity\": \"sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"p-try\": \"^2.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/sindresorhus\"\n      }\n    },\n    \"node_modules/webpack/node_modules/p-locate\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz\",\n      \"integrity\": \"sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"p-limit\": \"^2.2.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/webpack/node_modules/p-try\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz\",\n      \"integrity\": \"sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/webpack/node_modules/path-exists\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz\",\n      \"integrity\": \"sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/webpack/node_modules/pkg-dir\": {\n      \"version\": \"4.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz\",\n      \"integrity\": \"sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"find-up\": \"^4.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/webpack/node_modules/schema-utils\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz\",\n      \"integrity\": \"sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"@types/json-schema\": \"^7.0.6\",\n        \"ajv\": \"^6.12.5\",\n        \"ajv-keywords\": \"^3.5.2\"\n      },\n      \"engines\": {\n        \"node\": \">= 10.13.0\"\n      },\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/webpack\"\n      }\n    },\n    \"node_modules/webpack/node_modules/source-map\": {\n      \"version\": \"0.6.1\",\n      \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\",\n      \"integrity\": \"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/webpack/node_modules/tapable\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/tapable/-/tapable-2.1.1.tgz\",\n      \"integrity\": \"sha512-Wib1S8m2wdpLbmQz0RBEVosIyvb/ykfKXf3ZIDqvWoMg/zTNm6G/tDSuUM61J1kNCDXWJrLHGSFeMhAG+gAGpQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/webpack/node_modules/webpack-sources\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.2.0.tgz\",\n      \"integrity\": \"sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"source-list-map\": \"^2.0.1\",\n        \"source-map\": \"^0.6.1\"\n      },\n      \"engines\": {\n        \"node\": \">=10.13.0\"\n      }\n    },\n    \"node_modules/which\": {\n      \"version\": \"2.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/which/-/which-2.0.2.tgz\",\n      \"integrity\": \"sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"isexe\": \"^2.0.0\"\n      },\n      \"bin\": {\n        \"node-which\": \"bin/node-which\"\n      },\n      \"engines\": {\n        \"node\": \">= 8\"\n      }\n    },\n    \"node_modules/word-wrap\": {\n      \"version\": \"1.2.3\",\n      \"resolved\": \"https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz\",\n      \"integrity\": \"sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==\",\n      \"dev\": true,\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/wordwrapjs\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.0.tgz\",\n      \"integrity\": \"sha512-Svqw723a3R34KvsMgpjFBYCgNOSdcW3mQFK4wIfhGQhtaFVOJmdYoXgi63ne3dTlWgatVcUc7t4HtQ/+bUVIzQ==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"reduce-flatten\": \"^2.0.0\",\n        \"typical\": \"^5.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8.0.0\"\n      }\n    },\n    \"node_modules/wrappy\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz\",\n      \"integrity\": \"sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=\",\n      \"dev\": true\n    },\n    \"node_modules/write\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/write/-/write-1.0.3.tgz\",\n      \"integrity\": \"sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==\",\n      \"dev\": true,\n      \"dependencies\": {\n        \"mkdirp\": \"^0.5.1\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/yallist\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz\",\n      \"integrity\": \"sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==\",\n      \"dev\": true\n    },\n    \"node_modules/yn\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/yn/-/yn-3.1.1.tgz\",\n      \"integrity\": \"sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==\",\n      \"dev\": true,\n      \"peer\": true,\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    }\n  },\n  \"dependencies\": {\n    \"@babel/code-frame\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz\",\n      \"integrity\": \"sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/highlight\": \"^7.10.4\"\n      }\n    },\n    \"@babel/compat-data\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.7.tgz\",\n      \"integrity\": \"sha512-YaxPMGs/XIWtYqrdEOZOCPsVWfEoriXopnsz3/i7apYPXQ3698UFhS6dVT1KN5qOsWmVgw/FOrmQgpRaZayGsw==\",\n      \"dev\": true\n    },\n    \"@babel/core\": {\n      \"version\": \"7.12.9\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz\",\n      \"integrity\": \"sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/code-frame\": \"^7.10.4\",\n        \"@babel/generator\": \"^7.12.5\",\n        \"@babel/helper-module-transforms\": \"^7.12.1\",\n        \"@babel/helpers\": \"^7.12.5\",\n        \"@babel/parser\": \"^7.12.7\",\n        \"@babel/template\": \"^7.12.7\",\n        \"@babel/traverse\": \"^7.12.9\",\n        \"@babel/types\": \"^7.12.7\",\n        \"convert-source-map\": \"^1.7.0\",\n        \"debug\": \"^4.1.0\",\n        \"gensync\": \"^1.0.0-beta.1\",\n        \"json5\": \"^2.1.2\",\n        \"lodash\": \"^4.17.19\",\n        \"resolve\": \"^1.3.2\",\n        \"semver\": \"^5.4.1\",\n        \"source-map\": \"^0.5.0\"\n      }\n    },\n    \"@babel/eslint-parser\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.12.1.tgz\",\n      \"integrity\": \"sha512-cc7WQHnHQY3++/bghgbDtPx+5bf6xTsokyGzV6Qzh65NLz/unv+mPQuACkQ9GFhIhcTFv6yqwNaEcfX7EkOEsg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"eslint-scope\": \"5.1.0\",\n        \"eslint-visitor-keys\": \"^1.3.0\",\n        \"semver\": \"^6.3.0\"\n      },\n      \"dependencies\": {\n        \"semver\": {\n          \"version\": \"6.3.0\",\n          \"resolved\": \"https://registry.npmjs.org/semver/-/semver-6.3.0.tgz\",\n          \"integrity\": \"sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==\",\n          \"dev\": true\n        }\n      }\n    },\n    \"@babel/eslint-plugin\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/eslint-plugin/-/eslint-plugin-7.12.1.tgz\",\n      \"integrity\": \"sha512-rOjrD5yupTYCO4x0kEbQmi/NsaD+VGOD/9Cvso64WMVPY2y6o5Nvw2sqFWdeSEBdR1Dsa07YjplBs067x5YbXg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"eslint-rule-composer\": \"^0.3.0\"\n      }\n    },\n    \"@babel/generator\": {\n      \"version\": \"7.12.5\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/generator/-/generator-7.12.5.tgz\",\n      \"integrity\": \"sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.12.5\",\n        \"jsesc\": \"^2.5.1\",\n        \"source-map\": \"^0.5.0\"\n      }\n    },\n    \"@babel/helper-annotate-as-pure\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz\",\n      \"integrity\": \"sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.10.4\"\n      }\n    },\n    \"@babel/helper-builder-binary-assignment-operator-visitor\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz\",\n      \"integrity\": \"sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-explode-assignable-expression\": \"^7.10.4\",\n        \"@babel/types\": \"^7.10.4\"\n      }\n    },\n    \"@babel/helper-compilation-targets\": {\n      \"version\": \"7.12.5\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.5.tgz\",\n      \"integrity\": \"sha512-+qH6NrscMolUlzOYngSBMIOQpKUGPPsc61Bu5W10mg84LxZ7cmvnBHzARKbDoFxVvqqAbj6Tg6N7bSrWSPXMyw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/compat-data\": \"^7.12.5\",\n        \"@babel/helper-validator-option\": \"^7.12.1\",\n        \"browserslist\": \"^4.14.5\",\n        \"semver\": \"^5.5.0\"\n      }\n    },\n    \"@babel/helper-create-class-features-plugin\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz\",\n      \"integrity\": \"sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-function-name\": \"^7.10.4\",\n        \"@babel/helper-member-expression-to-functions\": \"^7.12.1\",\n        \"@babel/helper-optimise-call-expression\": \"^7.10.4\",\n        \"@babel/helper-replace-supers\": \"^7.12.1\",\n        \"@babel/helper-split-export-declaration\": \"^7.10.4\"\n      }\n    },\n    \"@babel/helper-create-regexp-features-plugin\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.7.tgz\",\n      \"integrity\": \"sha512-idnutvQPdpbduutvi3JVfEgcVIHooQnhvhx0Nk9isOINOIGYkZea1Pk2JlJRiUnMefrlvr0vkByATBY/mB4vjQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-annotate-as-pure\": \"^7.10.4\",\n        \"regexpu-core\": \"^4.7.1\"\n      }\n    },\n    \"@babel/helper-define-map\": {\n      \"version\": \"7.10.5\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz\",\n      \"integrity\": \"sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-function-name\": \"^7.10.4\",\n        \"@babel/types\": \"^7.10.5\",\n        \"lodash\": \"^4.17.19\"\n      }\n    },\n    \"@babel/helper-explode-assignable-expression\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz\",\n      \"integrity\": \"sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.12.1\"\n      }\n    },\n    \"@babel/helper-function-name\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz\",\n      \"integrity\": \"sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-get-function-arity\": \"^7.10.4\",\n        \"@babel/template\": \"^7.10.4\",\n        \"@babel/types\": \"^7.10.4\"\n      }\n    },\n    \"@babel/helper-get-function-arity\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz\",\n      \"integrity\": \"sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.10.4\"\n      }\n    },\n    \"@babel/helper-hoist-variables\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz\",\n      \"integrity\": \"sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.10.4\"\n      }\n    },\n    \"@babel/helper-member-expression-to-functions\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz\",\n      \"integrity\": \"sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.12.7\"\n      }\n    },\n    \"@babel/helper-module-imports\": {\n      \"version\": \"7.12.5\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz\",\n      \"integrity\": \"sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.12.5\"\n      }\n    },\n    \"@babel/helper-module-transforms\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz\",\n      \"integrity\": \"sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-module-imports\": \"^7.12.1\",\n        \"@babel/helper-replace-supers\": \"^7.12.1\",\n        \"@babel/helper-simple-access\": \"^7.12.1\",\n        \"@babel/helper-split-export-declaration\": \"^7.11.0\",\n        \"@babel/helper-validator-identifier\": \"^7.10.4\",\n        \"@babel/template\": \"^7.10.4\",\n        \"@babel/traverse\": \"^7.12.1\",\n        \"@babel/types\": \"^7.12.1\",\n        \"lodash\": \"^4.17.19\"\n      }\n    },\n    \"@babel/helper-optimise-call-expression\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.7.tgz\",\n      \"integrity\": \"sha512-I5xc9oSJ2h59OwyUqjv95HRyzxj53DAubUERgQMrpcCEYQyToeHA+NEcUEsVWB4j53RDeskeBJ0SgRAYHDBckw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.12.7\"\n      }\n    },\n    \"@babel/helper-plugin-utils\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz\",\n      \"integrity\": \"sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==\",\n      \"dev\": true\n    },\n    \"@babel/helper-remap-async-to-generator\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz\",\n      \"integrity\": \"sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-annotate-as-pure\": \"^7.10.4\",\n        \"@babel/helper-wrap-function\": \"^7.10.4\",\n        \"@babel/types\": \"^7.12.1\"\n      }\n    },\n    \"@babel/helper-replace-supers\": {\n      \"version\": \"7.12.5\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.5.tgz\",\n      \"integrity\": \"sha512-5YILoed0ZyIpF4gKcpZitEnXEJ9UoDRki1Ey6xz46rxOzfNMAhVIJMoune1hmPVxh40LRv1+oafz7UsWX+vyWA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-member-expression-to-functions\": \"^7.12.1\",\n        \"@babel/helper-optimise-call-expression\": \"^7.10.4\",\n        \"@babel/traverse\": \"^7.12.5\",\n        \"@babel/types\": \"^7.12.5\"\n      }\n    },\n    \"@babel/helper-simple-access\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz\",\n      \"integrity\": \"sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.12.1\"\n      }\n    },\n    \"@babel/helper-skip-transparent-expression-wrappers\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz\",\n      \"integrity\": \"sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.12.1\"\n      }\n    },\n    \"@babel/helper-split-export-declaration\": {\n      \"version\": \"7.11.0\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz\",\n      \"integrity\": \"sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/types\": \"^7.11.0\"\n      }\n    },\n    \"@babel/helper-validator-identifier\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz\",\n      \"integrity\": \"sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==\",\n      \"dev\": true\n    },\n    \"@babel/helper-validator-option\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz\",\n      \"integrity\": \"sha512-YpJabsXlJVWP0USHjnC/AQDTLlZERbON577YUVO/wLpqyj6HAtVYnWaQaN0iUN+1/tWn3c+uKKXjRut5115Y2A==\",\n      \"dev\": true\n    },\n    \"@babel/helper-wrap-function\": {\n      \"version\": \"7.12.3\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz\",\n      \"integrity\": \"sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-function-name\": \"^7.10.4\",\n        \"@babel/template\": \"^7.10.4\",\n        \"@babel/traverse\": \"^7.10.4\",\n        \"@babel/types\": \"^7.10.4\"\n      }\n    },\n    \"@babel/helpers\": {\n      \"version\": \"7.12.5\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz\",\n      \"integrity\": \"sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/template\": \"^7.10.4\",\n        \"@babel/traverse\": \"^7.12.5\",\n        \"@babel/types\": \"^7.12.5\"\n      }\n    },\n    \"@babel/highlight\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz\",\n      \"integrity\": \"sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-validator-identifier\": \"^7.10.4\",\n        \"chalk\": \"^2.0.0\",\n        \"js-tokens\": \"^4.0.0\"\n      }\n    },\n    \"@babel/parser\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/parser/-/parser-7.12.7.tgz\",\n      \"integrity\": \"sha512-oWR02Ubp4xTLCAqPRiNIuMVgNO5Aif/xpXtabhzW2HWUD47XJsAB4Zd/Rg30+XeQA3juXigV7hlquOTmwqLiwg==\",\n      \"dev\": true\n    },\n    \"@babel/plugin-proposal-async-generator-functions\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz\",\n      \"integrity\": \"sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-remap-async-to-generator\": \"^7.12.1\",\n        \"@babel/plugin-syntax-async-generators\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-proposal-class-properties\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz\",\n      \"integrity\": \"sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-create-class-features-plugin\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-proposal-dynamic-import\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz\",\n      \"integrity\": \"sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-dynamic-import\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-proposal-export-namespace-from\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz\",\n      \"integrity\": \"sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-export-namespace-from\": \"^7.8.3\"\n      }\n    },\n    \"@babel/plugin-proposal-json-strings\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz\",\n      \"integrity\": \"sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-json-strings\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-proposal-logical-assignment-operators\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz\",\n      \"integrity\": \"sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-logical-assignment-operators\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-proposal-nullish-coalescing-operator\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz\",\n      \"integrity\": \"sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-nullish-coalescing-operator\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-proposal-numeric-separator\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.7.tgz\",\n      \"integrity\": \"sha512-8c+uy0qmnRTeukiGsjLGy6uVs/TFjJchGXUeBqlG4VWYOdJWkhhVPdQ3uHwbmalfJwv2JsV0qffXP4asRfL2SQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-numeric-separator\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-proposal-object-rest-spread\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz\",\n      \"integrity\": \"sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-object-rest-spread\": \"^7.8.0\",\n        \"@babel/plugin-transform-parameters\": \"^7.12.1\"\n      }\n    },\n    \"@babel/plugin-proposal-optional-catch-binding\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz\",\n      \"integrity\": \"sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/plugin-syntax-optional-catch-binding\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-proposal-optional-chaining\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.7.tgz\",\n      \"integrity\": \"sha512-4ovylXZ0PWmwoOvhU2vhnzVNnm88/Sm9nx7V8BPgMvAzn5zDou3/Awy0EjglyubVHasJj+XCEkr/r1X3P5elCA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-skip-transparent-expression-wrappers\": \"^7.12.1\",\n        \"@babel/plugin-syntax-optional-chaining\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-proposal-private-methods\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz\",\n      \"integrity\": \"sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-create-class-features-plugin\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-proposal-unicode-property-regex\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz\",\n      \"integrity\": \"sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-create-regexp-features-plugin\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-syntax-async-generators\": {\n      \"version\": \"7.8.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz\",\n      \"integrity\": \"sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-syntax-class-properties\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz\",\n      \"integrity\": \"sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-syntax-dynamic-import\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz\",\n      \"integrity\": \"sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-syntax-export-namespace-from\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz\",\n      \"integrity\": \"sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.3\"\n      }\n    },\n    \"@babel/plugin-syntax-json-strings\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz\",\n      \"integrity\": \"sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-syntax-logical-assignment-operators\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz\",\n      \"integrity\": \"sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-syntax-nullish-coalescing-operator\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz\",\n      \"integrity\": \"sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-syntax-numeric-separator\": {\n      \"version\": \"7.10.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz\",\n      \"integrity\": \"sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-syntax-object-rest-spread\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz\",\n      \"integrity\": \"sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-syntax-optional-catch-binding\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz\",\n      \"integrity\": \"sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-syntax-optional-chaining\": {\n      \"version\": \"7.8.3\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz\",\n      \"integrity\": \"sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.8.0\"\n      }\n    },\n    \"@babel/plugin-syntax-top-level-await\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz\",\n      \"integrity\": \"sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-arrow-functions\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz\",\n      \"integrity\": \"sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-async-to-generator\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz\",\n      \"integrity\": \"sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-module-imports\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-remap-async-to-generator\": \"^7.12.1\"\n      }\n    },\n    \"@babel/plugin-transform-block-scoped-functions\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz\",\n      \"integrity\": \"sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-block-scoping\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz\",\n      \"integrity\": \"sha512-zJyAC9sZdE60r1nVQHblcfCj29Dh2Y0DOvlMkcqSo0ckqjiCwNiUezUKw+RjOCwGfpLRwnAeQ2XlLpsnGkvv9w==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-classes\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz\",\n      \"integrity\": \"sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-annotate-as-pure\": \"^7.10.4\",\n        \"@babel/helper-define-map\": \"^7.10.4\",\n        \"@babel/helper-function-name\": \"^7.10.4\",\n        \"@babel/helper-optimise-call-expression\": \"^7.10.4\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-replace-supers\": \"^7.12.1\",\n        \"@babel/helper-split-export-declaration\": \"^7.10.4\",\n        \"globals\": \"^11.1.0\"\n      }\n    },\n    \"@babel/plugin-transform-computed-properties\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz\",\n      \"integrity\": \"sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-destructuring\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz\",\n      \"integrity\": \"sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-dotall-regex\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz\",\n      \"integrity\": \"sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-create-regexp-features-plugin\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-duplicate-keys\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz\",\n      \"integrity\": \"sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-exponentiation-operator\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz\",\n      \"integrity\": \"sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-builder-binary-assignment-operator-visitor\": \"^7.10.4\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-for-of\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz\",\n      \"integrity\": \"sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-function-name\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz\",\n      \"integrity\": \"sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-function-name\": \"^7.10.4\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-literals\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz\",\n      \"integrity\": \"sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-member-expression-literals\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz\",\n      \"integrity\": \"sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-modules-amd\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz\",\n      \"integrity\": \"sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-module-transforms\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"babel-plugin-dynamic-import-node\": \"^2.3.3\"\n      }\n    },\n    \"@babel/plugin-transform-modules-commonjs\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz\",\n      \"integrity\": \"sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-module-transforms\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-simple-access\": \"^7.12.1\",\n        \"babel-plugin-dynamic-import-node\": \"^2.3.3\"\n      }\n    },\n    \"@babel/plugin-transform-modules-systemjs\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz\",\n      \"integrity\": \"sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-hoist-variables\": \"^7.10.4\",\n        \"@babel/helper-module-transforms\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-validator-identifier\": \"^7.10.4\",\n        \"babel-plugin-dynamic-import-node\": \"^2.3.3\"\n      }\n    },\n    \"@babel/plugin-transform-modules-umd\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz\",\n      \"integrity\": \"sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-module-transforms\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-named-capturing-groups-regex\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz\",\n      \"integrity\": \"sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-create-regexp-features-plugin\": \"^7.12.1\"\n      }\n    },\n    \"@babel/plugin-transform-new-target\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz\",\n      \"integrity\": \"sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-object-super\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz\",\n      \"integrity\": \"sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-replace-supers\": \"^7.12.1\"\n      }\n    },\n    \"@babel/plugin-transform-parameters\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz\",\n      \"integrity\": \"sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-property-literals\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz\",\n      \"integrity\": \"sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-regenerator\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz\",\n      \"integrity\": \"sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==\",\n      \"dev\": true,\n      \"requires\": {\n        \"regenerator-transform\": \"^0.14.2\"\n      }\n    },\n    \"@babel/plugin-transform-reserved-words\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz\",\n      \"integrity\": \"sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-shorthand-properties\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz\",\n      \"integrity\": \"sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-spread\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz\",\n      \"integrity\": \"sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-skip-transparent-expression-wrappers\": \"^7.12.1\"\n      }\n    },\n    \"@babel/plugin-transform-sticky-regex\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.7.tgz\",\n      \"integrity\": \"sha512-VEiqZL5N/QvDbdjfYQBhruN0HYjSPjC4XkeqW4ny/jNtH9gcbgaqBIXYEZCNnESMAGs0/K/R7oFGMhOyu/eIxg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-template-literals\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz\",\n      \"integrity\": \"sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-typeof-symbol\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.1.tgz\",\n      \"integrity\": \"sha512-EPGgpGy+O5Kg5pJFNDKuxt9RdmTgj5sgrus2XVeMp/ZIbOESadgILUbm50SNpghOh3/6yrbsH+NB5+WJTmsA7Q==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-unicode-escapes\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz\",\n      \"integrity\": \"sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/plugin-transform-unicode-regex\": {\n      \"version\": \"7.12.1\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz\",\n      \"integrity\": \"sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-create-regexp-features-plugin\": \"^7.12.1\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\"\n      }\n    },\n    \"@babel/preset-env\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.7.tgz\",\n      \"integrity\": \"sha512-OnNdfAr1FUQg7ksb7bmbKoby4qFOHw6DKWWUNB9KqnnCldxhxJlP+21dpyaWFmf2h0rTbOkXJtAGevY3XW1eew==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/compat-data\": \"^7.12.7\",\n        \"@babel/helper-compilation-targets\": \"^7.12.5\",\n        \"@babel/helper-module-imports\": \"^7.12.5\",\n        \"@babel/helper-plugin-utils\": \"^7.10.4\",\n        \"@babel/helper-validator-option\": \"^7.12.1\",\n        \"@babel/plugin-proposal-async-generator-functions\": \"^7.12.1\",\n        \"@babel/plugin-proposal-class-properties\": \"^7.12.1\",\n        \"@babel/plugin-proposal-dynamic-import\": \"^7.12.1\",\n        \"@babel/plugin-proposal-export-namespace-from\": \"^7.12.1\",\n        \"@babel/plugin-proposal-json-strings\": \"^7.12.1\",\n        \"@babel/plugin-proposal-logical-assignment-operators\": \"^7.12.1\",\n        \"@babel/plugin-proposal-nullish-coalescing-operator\": \"^7.12.1\",\n        \"@babel/plugin-proposal-numeric-separator\": \"^7.12.7\",\n        \"@babel/plugin-proposal-object-rest-spread\": \"^7.12.1\",\n        \"@babel/plugin-proposal-optional-catch-binding\": \"^7.12.1\",\n        \"@babel/plugin-proposal-optional-chaining\": \"^7.12.7\",\n        \"@babel/plugin-proposal-private-methods\": \"^7.12.1\",\n        \"@babel/plugin-proposal-unicode-property-regex\": \"^7.12.1\",\n        \"@babel/plugin-syntax-async-generators\": \"^7.8.0\",\n        \"@babel/plugin-syntax-class-properties\": \"^7.12.1\",\n        \"@babel/plugin-syntax-dynamic-import\": \"^7.8.0\",\n        \"@babel/plugin-syntax-export-namespace-from\": \"^7.8.3\",\n        \"@babel/plugin-syntax-json-strings\": \"^7.8.0\",\n        \"@babel/plugin-syntax-logical-assignment-operators\": \"^7.10.4\",\n        \"@babel/plugin-syntax-nullish-coalescing-operator\": \"^7.8.0\",\n        \"@babel/plugin-syntax-numeric-separator\": \"^7.10.4\",\n        \"@babel/plugin-syntax-object-rest-spread\": \"^7.8.0\",\n        \"@babel/plugin-syntax-optional-catch-binding\": \"^7.8.0\",\n        \"@babel/plugin-syntax-optional-chaining\": \"^7.8.0\",\n        \"@babel/plugin-syntax-top-level-await\": \"^7.12.1\",\n        \"@babel/plugin-transform-arrow-functions\": \"^7.12.1\",\n        \"@babel/plugin-transform-async-to-generator\": \"^7.12.1\",\n        \"@babel/plugin-transform-block-scoped-functions\": \"^7.12.1\",\n        \"@babel/plugin-transform-block-scoping\": \"^7.12.1\",\n        \"@babel/plugin-transform-classes\": \"^7.12.1\",\n        \"@babel/plugin-transform-computed-properties\": \"^7.12.1\",\n        \"@babel/plugin-transform-destructuring\": \"^7.12.1\",\n        \"@babel/plugin-transform-dotall-regex\": \"^7.12.1\",\n        \"@babel/plugin-transform-duplicate-keys\": \"^7.12.1\",\n        \"@babel/plugin-transform-exponentiation-operator\": \"^7.12.1\",\n        \"@babel/plugin-transform-for-of\": \"^7.12.1\",\n        \"@babel/plugin-transform-function-name\": \"^7.12.1\",\n        \"@babel/plugin-transform-literals\": \"^7.12.1\",\n        \"@babel/plugin-transform-member-expression-literals\": \"^7.12.1\",\n        \"@babel/plugin-transform-modules-amd\": \"^7.12.1\",\n        \"@babel/plugin-transform-modules-commonjs\": \"^7.12.1\",\n        \"@babel/plugin-transform-modules-systemjs\": \"^7.12.1\",\n        \"@babel/plugin-transform-modules-umd\": \"^7.12.1\",\n        \"@babel/plugin-transform-named-capturing-groups-regex\": \"^7.12.1\",\n        \"@babel/plugin-transform-new-target\": \"^7.12.1\",\n        \"@babel/plugin-transform-object-super\": \"^7.12.1\",\n        \"@babel/plugin-transform-parameters\": \"^7.12.1\",\n        \"@babel/plugin-transform-property-literals\": \"^7.12.1\",\n        \"@babel/plugin-transform-regenerator\": \"^7.12.1\",\n        \"@babel/plugin-transform-reserved-words\": \"^7.12.1\",\n        \"@babel/plugin-transform-shorthand-properties\": \"^7.12.1\",\n        \"@babel/plugin-transform-spread\": \"^7.12.1\",\n        \"@babel/plugin-transform-sticky-regex\": \"^7.12.7\",\n        \"@babel/plugin-transform-template-literals\": \"^7.12.1\",\n        \"@babel/plugin-transform-typeof-symbol\": \"^7.12.1\",\n        \"@babel/plugin-transform-unicode-escapes\": \"^7.12.1\",\n        \"@babel/plugin-transform-unicode-regex\": \"^7.12.1\",\n        \"@babel/preset-modules\": \"^0.1.3\",\n        \"@babel/types\": \"^7.12.7\",\n        \"core-js-compat\": \"^3.7.0\",\n        \"semver\": \"^5.5.0\"\n      }\n    },\n    \"@babel/preset-modules\": {\n      \"version\": \"0.1.4\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz\",\n      \"integrity\": \"sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-plugin-utils\": \"^7.0.0\",\n        \"@babel/plugin-proposal-unicode-property-regex\": \"^7.4.4\",\n        \"@babel/plugin-transform-dotall-regex\": \"^7.4.4\",\n        \"@babel/types\": \"^7.4.4\",\n        \"esutils\": \"^2.0.2\"\n      }\n    },\n    \"@babel/runtime\": {\n      \"version\": \"7.12.5\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz\",\n      \"integrity\": \"sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"regenerator-runtime\": \"^0.13.4\"\n      }\n    },\n    \"@babel/template\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz\",\n      \"integrity\": \"sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/code-frame\": \"^7.10.4\",\n        \"@babel/parser\": \"^7.12.7\",\n        \"@babel/types\": \"^7.12.7\"\n      }\n    },\n    \"@babel/traverse\": {\n      \"version\": \"7.12.9\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.9.tgz\",\n      \"integrity\": \"sha512-iX9ajqnLdoU1s1nHt36JDI9KG4k+vmI8WgjK5d+aDTwQbL2fUnzedNedssA645Ede3PM2ma1n8Q4h2ohwXgMXw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/code-frame\": \"^7.10.4\",\n        \"@babel/generator\": \"^7.12.5\",\n        \"@babel/helper-function-name\": \"^7.10.4\",\n        \"@babel/helper-split-export-declaration\": \"^7.11.0\",\n        \"@babel/parser\": \"^7.12.7\",\n        \"@babel/types\": \"^7.12.7\",\n        \"debug\": \"^4.1.0\",\n        \"globals\": \"^11.1.0\",\n        \"lodash\": \"^4.17.19\"\n      }\n    },\n    \"@babel/types\": {\n      \"version\": \"7.12.7\",\n      \"resolved\": \"https://registry.npmjs.org/@babel/types/-/types-7.12.7.tgz\",\n      \"integrity\": \"sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/helper-validator-identifier\": \"^7.10.4\",\n        \"lodash\": \"^4.17.19\",\n        \"to-fast-properties\": \"^2.0.0\"\n      }\n    },\n    \"@cspotcode/source-map-support\": {\n      \"version\": \"0.8.1\",\n      \"resolved\": \"https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz\",\n      \"integrity\": \"sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==\",\n      \"dev\": true,\n      \"peer\": true,\n      \"requires\": {\n        \"@jridgewell/trace-mapping\": \"0.3.9\"\n      },\n      \"dependencies\": {\n        \"@jridgewell/trace-mapping\": {\n          \"version\": \"0.3.9\",\n          \"resolved\": \"https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz\",\n          \"integrity\": \"sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==\",\n          \"dev\": true,\n          \"peer\": true,\n          \"requires\": {\n            \"@jridgewell/resolve-uri\": \"^3.0.3\",\n            \"@jridgewell/sourcemap-codec\": \"^1.4.10\"\n          }\n        }\n      }\n    },\n    \"@eslint/eslintrc\": {\n      \"version\": \"0.2.1\",\n      \"resolved\": \"https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.1.tgz\",\n      \"integrity\": \"sha512-XRUeBZ5zBWLYgSANMpThFddrZZkEbGHgUdt5UJjZfnlN9BGCiUBrf+nvbRupSjMvqzwnQN0qwCmOxITt1cfywA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"ajv\": \"^6.12.4\",\n        \"debug\": \"^4.1.1\",\n        \"espree\": \"^7.3.0\",\n        \"globals\": \"^12.1.0\",\n        \"ignore\": \"^4.0.6\",\n        \"import-fresh\": \"^3.2.1\",\n        \"js-yaml\": \"^3.13.1\",\n        \"lodash\": \"^4.17.19\",\n        \"minimatch\": \"^3.0.4\",\n        \"strip-json-comments\": \"^3.1.1\"\n      },\n      \"dependencies\": {\n        \"globals\": {\n          \"version\": \"12.4.0\",\n          \"resolved\": \"https://registry.npmjs.org/globals/-/globals-12.4.0.tgz\",\n          \"integrity\": \"sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==\",\n          \"dev\": true,\n          \"requires\": {\n            \"type-fest\": \"^0.8.1\"\n          }\n        }\n      }\n    },\n    \"@fortawesome/fontawesome-common-types\": {\n      \"version\": \"0.2.32\",\n      \"resolved\": \"https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.32.tgz\",\n      \"integrity\": \"sha512-ux2EDjKMpcdHBVLi/eWZynnPxs0BtFVXJkgHIxXRl+9ZFaHPvYamAfCzeeQFqHRjuJtX90wVnMRaMQAAlctz3w==\"\n    },\n    \"@fortawesome/fontawesome-svg-core\": {\n      \"version\": \"1.2.32\",\n      \"resolved\": \"https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.32.tgz\",\n      \"integrity\": \"sha512-XjqyeLCsR/c/usUpdWcOdVtWFVjPbDFBTQkn2fQRrWhhUoxriQohO2RWDxLyUM8XpD+Zzg5xwJ8gqTYGDLeGaQ==\",\n      \"requires\": {\n        \"@fortawesome/fontawesome-common-types\": \"^0.2.32\"\n      }\n    },\n    \"@fortawesome/free-regular-svg-icons\": {\n      \"version\": \"5.15.1\",\n      \"resolved\": \"https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.1.tgz\",\n      \"integrity\": \"sha512-eD9NWFy89e7SVVtrLedJUxIpCBGhd4x7s7dhesokjyo1Tw62daqN5UcuAGu1NrepLLq1IeAYUVfWwnOjZ/j3HA==\",\n      \"requires\": {\n        \"@fortawesome/fontawesome-common-types\": \"^0.2.32\"\n      }\n    },\n    \"@fortawesome/free-solid-svg-icons\": {\n      \"version\": \"5.15.1\",\n      \"resolved\": \"https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.1.tgz\",\n      \"integrity\": \"sha512-EFMuKtzRMNbvjab/SvJBaOOpaqJfdSap/Nl6hst7CgrJxwfORR1drdTV6q1Ib/JVzq4xObdTDcT6sqTaXMqfdg==\",\n      \"requires\": {\n        \"@fortawesome/fontawesome-common-types\": \"^0.2.32\"\n      }\n    },\n    \"@jridgewell/gen-mapping\": {\n      \"version\": \"0.3.2\",\n      \"resolved\": \"https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz\",\n      \"integrity\": \"sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@jridgewell/set-array\": \"^1.0.1\",\n        \"@jridgewell/sourcemap-codec\": \"^1.4.10\",\n        \"@jridgewell/trace-mapping\": \"^0.3.9\"\n      }\n    },\n    \"@jridgewell/resolve-uri\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz\",\n      \"integrity\": \"sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==\",\n      \"dev\": true\n    },\n    \"@jridgewell/set-array\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz\",\n      \"integrity\": \"sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==\",\n      \"dev\": true\n    },\n    \"@jridgewell/source-map\": {\n      \"version\": \"0.3.2\",\n      \"resolved\": \"https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz\",\n      \"integrity\": \"sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@jridgewell/gen-mapping\": \"^0.3.0\",\n        \"@jridgewell/trace-mapping\": \"^0.3.9\"\n      }\n    },\n    \"@jridgewell/sourcemap-codec\": {\n      \"version\": \"1.4.14\",\n      \"resolved\": \"https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz\",\n      \"integrity\": \"sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==\",\n      \"dev\": true\n    },\n    \"@jridgewell/trace-mapping\": {\n      \"version\": \"0.3.14\",\n      \"resolved\": \"https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz\",\n      \"integrity\": \"sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@jridgewell/resolve-uri\": \"^3.0.3\",\n        \"@jridgewell/sourcemap-codec\": \"^1.4.10\"\n      }\n    },\n    \"@tsconfig/node10\": {\n      \"version\": \"1.0.9\",\n      \"resolved\": \"https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz\",\n      \"integrity\": \"sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"@tsconfig/node12\": {\n      \"version\": \"1.0.11\",\n      \"resolved\": \"https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz\",\n      \"integrity\": \"sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"@tsconfig/node14\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz\",\n      \"integrity\": \"sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"@tsconfig/node16\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz\",\n      \"integrity\": \"sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"@types/anymatch\": {\n      \"version\": \"1.3.1\",\n      \"resolved\": \"https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz\",\n      \"integrity\": \"sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==\",\n      \"dev\": true\n    },\n    \"@types/eslint\": {\n      \"version\": \"7.2.5\",\n      \"resolved\": \"https://registry.npmjs.org/@types/eslint/-/eslint-7.2.5.tgz\",\n      \"integrity\": \"sha512-Dc6ar9x16BdaR3NSxSF7T4IjL9gxxViJq8RmFd+2UAyA+K6ck2W+gUwfgpG/y9TPyUuBL35109bbULpEynvltA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@types/estree\": \"*\",\n        \"@types/json-schema\": \"*\"\n      }\n    },\n    \"@types/eslint-scope\": {\n      \"version\": \"3.7.0\",\n      \"resolved\": \"https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.0.tgz\",\n      \"integrity\": \"sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@types/eslint\": \"*\",\n        \"@types/estree\": \"*\"\n      }\n    },\n    \"@types/estree\": {\n      \"version\": \"0.0.45\",\n      \"resolved\": \"https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz\",\n      \"integrity\": \"sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==\",\n      \"dev\": true\n    },\n    \"@types/html-minifier-terser\": {\n      \"version\": \"5.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz\",\n      \"integrity\": \"sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==\",\n      \"dev\": true\n    },\n    \"@types/json-schema\": {\n      \"version\": \"7.0.6\",\n      \"resolved\": \"https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz\",\n      \"integrity\": \"sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==\",\n      \"dev\": true\n    },\n    \"@types/json5\": {\n      \"version\": \"0.0.29\",\n      \"resolved\": \"https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz\",\n      \"integrity\": \"sha1-7ihweulOEdK4J7y+UnC86n8+ce4=\",\n      \"dev\": true\n    },\n    \"@types/node\": {\n      \"version\": \"14.14.9\",\n      \"resolved\": \"https://registry.npmjs.org/@types/node/-/node-14.14.9.tgz\",\n      \"integrity\": \"sha512-JsoLXFppG62tWTklIoO4knA+oDTYsmqWxHRvd4lpmfQRNhX6osheUOWETP2jMoV/2bEHuMra8Pp3Dmo/stBFcw==\",\n      \"dev\": true\n    },\n    \"@types/source-list-map\": {\n      \"version\": \"0.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz\",\n      \"integrity\": \"sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==\",\n      \"dev\": true\n    },\n    \"@types/tapable\": {\n      \"version\": \"1.0.6\",\n      \"resolved\": \"https://registry.npmjs.org/@types/tapable/-/tapable-1.0.6.tgz\",\n      \"integrity\": \"sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==\",\n      \"dev\": true\n    },\n    \"@types/uglify-js\": {\n      \"version\": \"3.11.1\",\n      \"resolved\": \"https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.11.1.tgz\",\n      \"integrity\": \"sha512-7npvPKV+jINLu1SpSYVWG8KvyJBhBa8tmzMMdDoVc2pWUYHN8KIXlPJhjJ4LT97c4dXJA2SHL/q6ADbDriZN+Q==\",\n      \"dev\": true,\n      \"requires\": {\n        \"source-map\": \"^0.6.1\"\n      },\n      \"dependencies\": {\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==\",\n          \"dev\": true\n        }\n      }\n    },\n    \"@types/webpack\": {\n      \"version\": \"4.41.25\",\n      \"resolved\": \"https://registry.npmjs.org/@types/webpack/-/webpack-4.41.25.tgz\",\n      \"integrity\": \"sha512-cr6kZ+4m9lp86ytQc1jPOJXgINQyz3kLLunZ57jznW+WIAL0JqZbGubQk4GlD42MuQL5JGOABrxdpqqWeovlVQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@types/anymatch\": \"*\",\n        \"@types/node\": \"*\",\n        \"@types/tapable\": \"*\",\n        \"@types/uglify-js\": \"*\",\n        \"@types/webpack-sources\": \"*\",\n        \"source-map\": \"^0.6.0\"\n      },\n      \"dependencies\": {\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==\",\n          \"dev\": true\n        }\n      }\n    },\n    \"@types/webpack-sources\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-2.0.0.tgz\",\n      \"integrity\": \"sha512-a5kPx98CNFRKQ+wqawroFunvFqv7GHm/3KOI52NY9xWADgc8smu4R6prt4EU/M4QfVjvgBkMqU4fBhw3QfMVkg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@types/node\": \"*\",\n        \"@types/source-list-map\": \"*\",\n        \"source-map\": \"^0.7.3\"\n      },\n      \"dependencies\": {\n        \"source-map\": {\n          \"version\": \"0.7.3\",\n          \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz\",\n          \"integrity\": \"sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==\",\n          \"dev\": true\n        }\n      }\n    },\n    \"@webassemblyjs/ast\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz\",\n      \"integrity\": \"sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/helper-module-context\": \"1.9.0\",\n        \"@webassemblyjs/helper-wasm-bytecode\": \"1.9.0\",\n        \"@webassemblyjs/wast-parser\": \"1.9.0\"\n      }\n    },\n    \"@webassemblyjs/floating-point-hex-parser\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz\",\n      \"integrity\": \"sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==\",\n      \"dev\": true\n    },\n    \"@webassemblyjs/helper-api-error\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz\",\n      \"integrity\": \"sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==\",\n      \"dev\": true\n    },\n    \"@webassemblyjs/helper-buffer\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz\",\n      \"integrity\": \"sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==\",\n      \"dev\": true\n    },\n    \"@webassemblyjs/helper-code-frame\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz\",\n      \"integrity\": \"sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/wast-printer\": \"1.9.0\"\n      }\n    },\n    \"@webassemblyjs/helper-fsm\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz\",\n      \"integrity\": \"sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==\",\n      \"dev\": true\n    },\n    \"@webassemblyjs/helper-module-context\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz\",\n      \"integrity\": \"sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/ast\": \"1.9.0\"\n      }\n    },\n    \"@webassemblyjs/helper-wasm-bytecode\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz\",\n      \"integrity\": \"sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==\",\n      \"dev\": true\n    },\n    \"@webassemblyjs/helper-wasm-section\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz\",\n      \"integrity\": \"sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/helper-buffer\": \"1.9.0\",\n        \"@webassemblyjs/helper-wasm-bytecode\": \"1.9.0\",\n        \"@webassemblyjs/wasm-gen\": \"1.9.0\"\n      }\n    },\n    \"@webassemblyjs/ieee754\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz\",\n      \"integrity\": \"sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@xtuc/ieee754\": \"^1.2.0\"\n      }\n    },\n    \"@webassemblyjs/leb128\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz\",\n      \"integrity\": \"sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@xtuc/long\": \"4.2.2\"\n      }\n    },\n    \"@webassemblyjs/utf8\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz\",\n      \"integrity\": \"sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==\",\n      \"dev\": true\n    },\n    \"@webassemblyjs/wasm-edit\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz\",\n      \"integrity\": \"sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/helper-buffer\": \"1.9.0\",\n        \"@webassemblyjs/helper-wasm-bytecode\": \"1.9.0\",\n        \"@webassemblyjs/helper-wasm-section\": \"1.9.0\",\n        \"@webassemblyjs/wasm-gen\": \"1.9.0\",\n        \"@webassemblyjs/wasm-opt\": \"1.9.0\",\n        \"@webassemblyjs/wasm-parser\": \"1.9.0\",\n        \"@webassemblyjs/wast-printer\": \"1.9.0\"\n      }\n    },\n    \"@webassemblyjs/wasm-gen\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz\",\n      \"integrity\": \"sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/helper-wasm-bytecode\": \"1.9.0\",\n        \"@webassemblyjs/ieee754\": \"1.9.0\",\n        \"@webassemblyjs/leb128\": \"1.9.0\",\n        \"@webassemblyjs/utf8\": \"1.9.0\"\n      }\n    },\n    \"@webassemblyjs/wasm-opt\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz\",\n      \"integrity\": \"sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/helper-buffer\": \"1.9.0\",\n        \"@webassemblyjs/wasm-gen\": \"1.9.0\",\n        \"@webassemblyjs/wasm-parser\": \"1.9.0\"\n      }\n    },\n    \"@webassemblyjs/wasm-parser\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz\",\n      \"integrity\": \"sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/helper-api-error\": \"1.9.0\",\n        \"@webassemblyjs/helper-wasm-bytecode\": \"1.9.0\",\n        \"@webassemblyjs/ieee754\": \"1.9.0\",\n        \"@webassemblyjs/leb128\": \"1.9.0\",\n        \"@webassemblyjs/utf8\": \"1.9.0\"\n      }\n    },\n    \"@webassemblyjs/wast-parser\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz\",\n      \"integrity\": \"sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/floating-point-hex-parser\": \"1.9.0\",\n        \"@webassemblyjs/helper-api-error\": \"1.9.0\",\n        \"@webassemblyjs/helper-code-frame\": \"1.9.0\",\n        \"@webassemblyjs/helper-fsm\": \"1.9.0\",\n        \"@xtuc/long\": \"4.2.2\"\n      }\n    },\n    \"@webassemblyjs/wast-printer\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz\",\n      \"integrity\": \"sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/wast-parser\": \"1.9.0\",\n        \"@xtuc/long\": \"4.2.2\"\n      }\n    },\n    \"@webpack-cli/info\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webpack-cli/info/-/info-1.1.0.tgz\",\n      \"integrity\": \"sha512-uNWSdaYHc+f3LdIZNwhdhkjjLDDl3jP2+XBqAq9H8DjrJUvlOKdP8TNruy1yEaDfgpAIgbSAN7pye4FEHg9tYQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"envinfo\": \"^7.7.3\"\n      }\n    },\n    \"@webpack-cli/serve\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.1.0.tgz\",\n      \"integrity\": \"sha512-7RfnMXCpJ/NThrhq4gYQYILB18xWyoQcBey81oIyVbmgbc6m5ZHHyFK+DyH7pLHJf0p14MxL4mTsoPAgBSTpIg==\",\n      \"dev\": true,\n      \"requires\": {}\n    },\n    \"@xtuc/ieee754\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz\",\n      \"integrity\": \"sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==\",\n      \"dev\": true\n    },\n    \"@xtuc/long\": {\n      \"version\": \"4.2.2\",\n      \"resolved\": \"https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz\",\n      \"integrity\": \"sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==\",\n      \"dev\": true\n    },\n    \"acorn\": {\n      \"version\": \"7.4.1\",\n      \"resolved\": \"https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz\",\n      \"integrity\": \"sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==\",\n      \"dev\": true\n    },\n    \"acorn-jsx\": {\n      \"version\": \"5.3.1\",\n      \"resolved\": \"https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz\",\n      \"integrity\": \"sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==\",\n      \"dev\": true,\n      \"requires\": {}\n    },\n    \"acorn-walk\": {\n      \"version\": \"8.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz\",\n      \"integrity\": \"sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"ajv\": {\n      \"version\": \"6.12.6\",\n      \"resolved\": \"https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz\",\n      \"integrity\": \"sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==\",\n      \"dev\": true,\n      \"requires\": {\n        \"fast-deep-equal\": \"^3.1.1\",\n        \"fast-json-stable-stringify\": \"^2.0.0\",\n        \"json-schema-traverse\": \"^0.4.1\",\n        \"uri-js\": \"^4.2.2\"\n      }\n    },\n    \"ajv-keywords\": {\n      \"version\": \"3.5.2\",\n      \"resolved\": \"https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz\",\n      \"integrity\": \"sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==\",\n      \"dev\": true,\n      \"requires\": {}\n    },\n    \"ansi-colors\": {\n      \"version\": \"4.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz\",\n      \"integrity\": \"sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==\",\n      \"dev\": true\n    },\n    \"ansi-regex\": {\n      \"version\": \"5.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz\",\n      \"integrity\": \"sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==\",\n      \"dev\": true\n    },\n    \"ansi-styles\": {\n      \"version\": \"3.2.1\",\n      \"resolved\": \"https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz\",\n      \"integrity\": \"sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"color-convert\": \"^1.9.0\"\n      }\n    },\n    \"anymatch\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz\",\n      \"integrity\": \"sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"normalize-path\": \"^3.0.0\",\n        \"picomatch\": \"^2.0.4\"\n      }\n    },\n    \"arg\": {\n      \"version\": \"4.1.3\",\n      \"resolved\": \"https://registry.npmjs.org/arg/-/arg-4.1.3.tgz\",\n      \"integrity\": \"sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"argparse\": {\n      \"version\": \"1.0.10\",\n      \"resolved\": \"https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz\",\n      \"integrity\": \"sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"sprintf-js\": \"~1.0.2\"\n      }\n    },\n    \"array-back\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/array-back/-/array-back-4.0.1.tgz\",\n      \"integrity\": \"sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg==\",\n      \"dev\": true\n    },\n    \"array-includes\": {\n      \"version\": \"3.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/array-includes/-/array-includes-3.1.2.tgz\",\n      \"integrity\": \"sha512-w2GspexNQpx+PutG3QpT437/BenZBj0M/MZGn5mzv/MofYqo0xmRHzn4lFsoDlWJ+THYsGJmFlW68WlDFx7VRw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"call-bind\": \"^1.0.0\",\n        \"define-properties\": \"^1.1.3\",\n        \"es-abstract\": \"^1.18.0-next.1\",\n        \"get-intrinsic\": \"^1.0.1\",\n        \"is-string\": \"^1.0.5\"\n      }\n    },\n    \"array.prototype.flat\": {\n      \"version\": \"1.2.4\",\n      \"resolved\": \"https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz\",\n      \"integrity\": \"sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"call-bind\": \"^1.0.0\",\n        \"define-properties\": \"^1.1.3\",\n        \"es-abstract\": \"^1.18.0-next.1\"\n      }\n    },\n    \"astral-regex\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz\",\n      \"integrity\": \"sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==\",\n      \"dev\": true\n    },\n    \"autoprefixer\": {\n      \"version\": \"10.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.0.2.tgz\",\n      \"integrity\": \"sha512-okBmu9OMdt6DNEcZmnl0IYVv8Xl/xYWRSnc2OJ9UJEOt1u30opG1B8aLsViqKryBaYv1SKB4f85fOGZs5zYxHQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"browserslist\": \"^4.14.7\",\n        \"caniuse-lite\": \"^1.0.30001157\",\n        \"colorette\": \"^1.2.1\",\n        \"normalize-range\": \"^0.1.2\",\n        \"num2fraction\": \"^1.2.2\",\n        \"postcss-value-parser\": \"^4.1.0\"\n      }\n    },\n    \"babel-loader\": {\n      \"version\": \"8.2.1\",\n      \"resolved\": \"https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.1.tgz\",\n      \"integrity\": \"sha512-dMF8sb2KQ8kJl21GUjkW1HWmcsL39GOV5vnzjqrCzEPNY0S0UfMLnumidiwIajDSBmKhYf5iRW+HXaM4cvCKBw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"find-cache-dir\": \"^2.1.0\",\n        \"loader-utils\": \"^1.4.0\",\n        \"make-dir\": \"^2.1.0\",\n        \"pify\": \"^4.0.1\",\n        \"schema-utils\": \"^2.6.5\"\n      }\n    },\n    \"babel-plugin-dynamic-import-node\": {\n      \"version\": \"2.3.3\",\n      \"resolved\": \"https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz\",\n      \"integrity\": \"sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"object.assign\": \"^4.1.0\"\n      }\n    },\n    \"balanced-match\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz\",\n      \"integrity\": \"sha1-ibTRmasr7kneFk6gK4nORi1xt2c=\",\n      \"dev\": true\n    },\n    \"big.js\": {\n      \"version\": \"5.2.2\",\n      \"resolved\": \"https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz\",\n      \"integrity\": \"sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==\",\n      \"dev\": true\n    },\n    \"binary-extensions\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz\",\n      \"integrity\": \"sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==\",\n      \"dev\": true\n    },\n    \"boolbase\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz\",\n      \"integrity\": \"sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==\",\n      \"dev\": true\n    },\n    \"bootstrap\": {\n      \"version\": \"4.5.3\",\n      \"resolved\": \"https://registry.npmjs.org/bootstrap/-/bootstrap-4.5.3.tgz\",\n      \"integrity\": \"sha512-o9ppKQioXGqhw8Z7mah6KdTYpNQY//tipnkxppWhPbiSWdD+1raYsnhwEZjkTHYbGee4cVQ0Rx65EhOY/HNLcQ==\",\n      \"requires\": {}\n    },\n    \"bootswatch\": {\n      \"version\": \"4.5.3\",\n      \"resolved\": \"https://registry.npmjs.org/bootswatch/-/bootswatch-4.5.3.tgz\",\n      \"integrity\": \"sha512-gaB3gBSAegmYbk97aVELKcSKVdPjWsSY4yCITkUt/SqbqjtMU/HtIUszb4O9vzdbrfuVXThc/qCXzjoJaAPgiQ==\"\n    },\n    \"brace-expansion\": {\n      \"version\": \"1.1.11\",\n      \"resolved\": \"https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz\",\n      \"integrity\": \"sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"balanced-match\": \"^1.0.0\",\n        \"concat-map\": \"0.0.1\"\n      }\n    },\n    \"braces\": {\n      \"version\": \"3.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/braces/-/braces-3.0.2.tgz\",\n      \"integrity\": \"sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==\",\n      \"dev\": true,\n      \"requires\": {\n        \"fill-range\": \"^7.0.1\"\n      }\n    },\n    \"browserslist\": {\n      \"version\": \"4.19.1\",\n      \"resolved\": \"https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz\",\n      \"integrity\": \"sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==\",\n      \"dev\": true,\n      \"requires\": {\n        \"caniuse-lite\": \"^1.0.30001286\",\n        \"electron-to-chromium\": \"^1.4.17\",\n        \"escalade\": \"^3.1.1\",\n        \"node-releases\": \"^2.0.1\",\n        \"picocolors\": \"^1.0.0\"\n      }\n    },\n    \"buffer-from\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz\",\n      \"integrity\": \"sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==\",\n      \"dev\": true\n    },\n    \"call-bind\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz\",\n      \"integrity\": \"sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==\",\n      \"dev\": true,\n      \"requires\": {\n        \"function-bind\": \"^1.1.1\",\n        \"get-intrinsic\": \"^1.0.0\"\n      }\n    },\n    \"callsites\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz\",\n      \"integrity\": \"sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==\",\n      \"dev\": true\n    },\n    \"camel-case\": {\n      \"version\": \"4.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/camel-case/-/camel-case-4.1.1.tgz\",\n      \"integrity\": \"sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q==\",\n      \"dev\": true,\n      \"requires\": {\n        \"pascal-case\": \"^3.1.1\",\n        \"tslib\": \"^1.10.0\"\n      }\n    },\n    \"camelcase\": {\n      \"version\": \"6.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz\",\n      \"integrity\": \"sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==\",\n      \"dev\": true\n    },\n    \"caniuse-lite\": {\n      \"version\": \"1.0.30001304\",\n      \"resolved\": \"https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001304.tgz\",\n      \"integrity\": \"sha512-bdsfZd6K6ap87AGqSHJP/s1V+U6Z5lyrcbBu3ovbCCf8cSYpwTtGrCBObMpJqwxfTbLW6YTIdbb1jEeTelcpYQ==\",\n      \"dev\": true\n    },\n    \"chalk\": {\n      \"version\": \"2.4.2\",\n      \"resolved\": \"https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz\",\n      \"integrity\": \"sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"ansi-styles\": \"^3.2.1\",\n        \"escape-string-regexp\": \"^1.0.5\",\n        \"supports-color\": \"^5.3.0\"\n      }\n    },\n    \"chokidar\": {\n      \"version\": \"3.4.3\",\n      \"resolved\": \"https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz\",\n      \"integrity\": \"sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"anymatch\": \"~3.1.1\",\n        \"braces\": \"~3.0.2\",\n        \"fsevents\": \"~2.1.2\",\n        \"glob-parent\": \"~5.1.0\",\n        \"is-binary-path\": \"~2.1.0\",\n        \"is-glob\": \"~4.0.1\",\n        \"normalize-path\": \"~3.0.0\",\n        \"readdirp\": \"~3.5.0\"\n      }\n    },\n    \"chrome-trace-event\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz\",\n      \"integrity\": \"sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"tslib\": \"^1.9.0\"\n      }\n    },\n    \"clean-css\": {\n      \"version\": \"4.2.3\",\n      \"resolved\": \"https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz\",\n      \"integrity\": \"sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"source-map\": \"~0.6.0\"\n      },\n      \"dependencies\": {\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==\",\n          \"dev\": true\n        }\n      }\n    },\n    \"color-convert\": {\n      \"version\": \"1.9.3\",\n      \"resolved\": \"https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz\",\n      \"integrity\": \"sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"color-name\": \"1.1.3\"\n      }\n    },\n    \"color-name\": {\n      \"version\": \"1.1.3\",\n      \"resolved\": \"https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz\",\n      \"integrity\": \"sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=\",\n      \"dev\": true\n    },\n    \"colorette\": {\n      \"version\": \"1.2.2\",\n      \"resolved\": \"https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz\",\n      \"integrity\": \"sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==\",\n      \"dev\": true\n    },\n    \"command-line-usage\": {\n      \"version\": \"6.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.1.tgz\",\n      \"integrity\": \"sha512-F59pEuAR9o1SF/bD0dQBDluhpT4jJQNWUHEuVBqpDmCUo6gPjCi+m9fCWnWZVR/oG6cMTUms4h+3NPl74wGXvA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"array-back\": \"^4.0.1\",\n        \"chalk\": \"^2.4.2\",\n        \"table-layout\": \"^1.0.1\",\n        \"typical\": \"^5.2.0\"\n      }\n    },\n    \"commander\": {\n      \"version\": \"4.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/commander/-/commander-4.1.1.tgz\",\n      \"integrity\": \"sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==\",\n      \"dev\": true\n    },\n    \"comment-parser\": {\n      \"version\": \"0.7.6\",\n      \"resolved\": \"https://registry.npmjs.org/comment-parser/-/comment-parser-0.7.6.tgz\",\n      \"integrity\": \"sha512-GKNxVA7/iuTnAqGADlTWX4tkhzxZKXp5fLJqKTlQLHkE65XDUKutZ3BHaJC5IGcper2tT3QRD1xr4o3jNpgXXg==\",\n      \"dev\": true\n    },\n    \"commondir\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz\",\n      \"integrity\": \"sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=\",\n      \"dev\": true\n    },\n    \"concat-map\": {\n      \"version\": \"0.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz\",\n      \"integrity\": \"sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=\",\n      \"dev\": true\n    },\n    \"contains-path\": {\n      \"version\": \"0.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz\",\n      \"integrity\": \"sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=\",\n      \"dev\": true\n    },\n    \"convert-source-map\": {\n      \"version\": \"1.7.0\",\n      \"resolved\": \"https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz\",\n      \"integrity\": \"sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"safe-buffer\": \"~5.1.1\"\n      }\n    },\n    \"core-js\": {\n      \"version\": \"3.7.0\",\n      \"resolved\": \"https://registry.npmjs.org/core-js/-/core-js-3.7.0.tgz\",\n      \"integrity\": \"sha512-NwS7fI5M5B85EwpWuIwJN4i/fbisQUwLwiSNUWeXlkAZ0sbBjLEvLvFLf1uzAUV66PcEPt4xCGCmOZSxVf3xzA==\",\n      \"dev\": true\n    },\n    \"core-js-compat\": {\n      \"version\": \"3.7.0\",\n      \"resolved\": \"https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.7.0.tgz\",\n      \"integrity\": \"sha512-V8yBI3+ZLDVomoWICO6kq/CD28Y4r1M7CWeO4AGpMdMfseu8bkSubBmUPySMGKRTS+su4XQ07zUkAsiu9FCWTg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"browserslist\": \"^4.14.6\",\n        \"semver\": \"7.0.0\"\n      },\n      \"dependencies\": {\n        \"semver\": {\n          \"version\": \"7.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/semver/-/semver-7.0.0.tgz\",\n          \"integrity\": \"sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==\",\n          \"dev\": true\n        }\n      }\n    },\n    \"cosmiconfig\": {\n      \"version\": \"8.1.3\",\n      \"resolved\": \"https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz\",\n      \"integrity\": \"sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"import-fresh\": \"^3.2.1\",\n        \"js-yaml\": \"^4.1.0\",\n        \"parse-json\": \"^5.0.0\",\n        \"path-type\": \"^4.0.0\"\n      },\n      \"dependencies\": {\n        \"argparse\": {\n          \"version\": \"2.0.1\",\n          \"resolved\": \"https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz\",\n          \"integrity\": \"sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==\",\n          \"dev\": true\n        },\n        \"js-yaml\": {\n          \"version\": \"4.1.0\",\n          \"resolved\": \"https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz\",\n          \"integrity\": \"sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==\",\n          \"dev\": true,\n          \"requires\": {\n            \"argparse\": \"^2.0.1\"\n          }\n        }\n      }\n    },\n    \"cosmiconfig-typescript-loader\": {\n      \"version\": \"4.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.3.0.tgz\",\n      \"integrity\": \"sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==\",\n      \"dev\": true,\n      \"requires\": {}\n    },\n    \"create-require\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz\",\n      \"integrity\": \"sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"cross-spawn\": {\n      \"version\": \"7.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz\",\n      \"integrity\": \"sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==\",\n      \"dev\": true,\n      \"requires\": {\n        \"path-key\": \"^3.1.0\",\n        \"shebang-command\": \"^2.0.0\",\n        \"which\": \"^2.0.1\"\n      }\n    },\n    \"css-loader\": {\n      \"version\": \"5.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/css-loader/-/css-loader-5.0.1.tgz\",\n      \"integrity\": \"sha512-cXc2ti9V234cq7rJzFKhirb2L2iPy8ZjALeVJAozXYz9te3r4eqLSixNAbMDJSgJEQywqXzs8gonxaboeKqwiw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"camelcase\": \"^6.2.0\",\n        \"cssesc\": \"^3.0.0\",\n        \"icss-utils\": \"^5.0.0\",\n        \"loader-utils\": \"^2.0.0\",\n        \"postcss\": \"^8.1.4\",\n        \"postcss-modules-extract-imports\": \"^3.0.0\",\n        \"postcss-modules-local-by-default\": \"^4.0.0\",\n        \"postcss-modules-scope\": \"^3.0.0\",\n        \"postcss-modules-values\": \"^4.0.0\",\n        \"postcss-value-parser\": \"^4.1.0\",\n        \"schema-utils\": \"^3.0.0\",\n        \"semver\": \"^7.3.2\"\n      },\n      \"dependencies\": {\n        \"loader-utils\": {\n          \"version\": \"2.0.4\",\n          \"resolved\": \"https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz\",\n          \"integrity\": \"sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==\",\n          \"dev\": true,\n          \"requires\": {\n            \"big.js\": \"^5.2.2\",\n            \"emojis-list\": \"^3.0.0\",\n            \"json5\": \"^2.1.2\"\n          }\n        },\n        \"schema-utils\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz\",\n          \"integrity\": \"sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==\",\n          \"dev\": true,\n          \"requires\": {\n            \"@types/json-schema\": \"^7.0.6\",\n            \"ajv\": \"^6.12.5\",\n            \"ajv-keywords\": \"^3.5.2\"\n          }\n        },\n        \"semver\": {\n          \"version\": \"7.3.2\",\n          \"resolved\": \"https://registry.npmjs.org/semver/-/semver-7.3.2.tgz\",\n          \"integrity\": \"sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==\",\n          \"dev\": true\n        }\n      }\n    },\n    \"css-select\": {\n      \"version\": \"4.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz\",\n      \"integrity\": \"sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"boolbase\": \"^1.0.0\",\n        \"css-what\": \"^6.0.1\",\n        \"domhandler\": \"^4.3.1\",\n        \"domutils\": \"^2.8.0\",\n        \"nth-check\": \"^2.0.1\"\n      }\n    },\n    \"css-what\": {\n      \"version\": \"6.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz\",\n      \"integrity\": \"sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==\",\n      \"dev\": true\n    },\n    \"cssesc\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz\",\n      \"integrity\": \"sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==\",\n      \"dev\": true\n    },\n    \"debug\": {\n      \"version\": \"4.3.1\",\n      \"resolved\": \"https://registry.npmjs.org/debug/-/debug-4.3.1.tgz\",\n      \"integrity\": \"sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"ms\": \"2.1.2\"\n      }\n    },\n    \"deep-extend\": {\n      \"version\": \"0.6.0\",\n      \"resolved\": \"https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz\",\n      \"integrity\": \"sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==\",\n      \"dev\": true\n    },\n    \"deep-is\": {\n      \"version\": \"0.1.3\",\n      \"resolved\": \"https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz\",\n      \"integrity\": \"sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=\",\n      \"dev\": true\n    },\n    \"define-properties\": {\n      \"version\": \"1.1.3\",\n      \"resolved\": \"https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz\",\n      \"integrity\": \"sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"object-keys\": \"^1.0.12\"\n      }\n    },\n    \"diff\": {\n      \"version\": \"4.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/diff/-/diff-4.0.2.tgz\",\n      \"integrity\": \"sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"doctrine\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz\",\n      \"integrity\": \"sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==\",\n      \"dev\": true,\n      \"requires\": {\n        \"esutils\": \"^2.0.2\"\n      }\n    },\n    \"dom-converter\": {\n      \"version\": \"0.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz\",\n      \"integrity\": \"sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"utila\": \"~0.4\"\n      }\n    },\n    \"dom-serializer\": {\n      \"version\": \"1.4.1\",\n      \"resolved\": \"https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz\",\n      \"integrity\": \"sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==\",\n      \"dev\": true,\n      \"requires\": {\n        \"domelementtype\": \"^2.0.1\",\n        \"domhandler\": \"^4.2.0\",\n        \"entities\": \"^2.0.0\"\n      }\n    },\n    \"domelementtype\": {\n      \"version\": \"2.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz\",\n      \"integrity\": \"sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==\",\n      \"dev\": true\n    },\n    \"domhandler\": {\n      \"version\": \"4.3.1\",\n      \"resolved\": \"https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz\",\n      \"integrity\": \"sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"domelementtype\": \"^2.2.0\"\n      }\n    },\n    \"domutils\": {\n      \"version\": \"2.8.0\",\n      \"resolved\": \"https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz\",\n      \"integrity\": \"sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==\",\n      \"dev\": true,\n      \"requires\": {\n        \"dom-serializer\": \"^1.0.1\",\n        \"domelementtype\": \"^2.2.0\",\n        \"domhandler\": \"^4.2.0\"\n      }\n    },\n    \"dot-case\": {\n      \"version\": \"3.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/dot-case/-/dot-case-3.0.3.tgz\",\n      \"integrity\": \"sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"no-case\": \"^3.0.3\",\n        \"tslib\": \"^1.10.0\"\n      }\n    },\n    \"electron-to-chromium\": {\n      \"version\": \"1.4.60\",\n      \"resolved\": \"https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.60.tgz\",\n      \"integrity\": \"sha512-h53hbEiKC6hijelDgxgkgAUC3PKyR7TmIfvjHnBjUGPMg/3sBuTyG6eDormw+lY24uUJvHkUPzB8dpK8b2u3Sw==\",\n      \"dev\": true\n    },\n    \"emoji-regex\": {\n      \"version\": \"7.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz\",\n      \"integrity\": \"sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==\",\n      \"dev\": true\n    },\n    \"emojis-list\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz\",\n      \"integrity\": \"sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==\",\n      \"dev\": true\n    },\n    \"end-of-stream\": {\n      \"version\": \"1.4.4\",\n      \"resolved\": \"https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz\",\n      \"integrity\": \"sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==\",\n      \"dev\": true,\n      \"requires\": {\n        \"once\": \"^1.4.0\"\n      }\n    },\n    \"enhanced-resolve\": {\n      \"version\": \"5.3.2\",\n      \"resolved\": \"https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.3.2.tgz\",\n      \"integrity\": \"sha512-G28GCrglCAH6+EqMN2D+Q2wCUS1O1vVQJBn8ME2I/Api41YBe4vLWWRBOUbwDH7vwzSZdljxwTRVqnf+sm6XqQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"graceful-fs\": \"^4.2.4\",\n        \"tapable\": \"^2.0.0\"\n      },\n      \"dependencies\": {\n        \"tapable\": {\n          \"version\": \"2.1.1\",\n          \"resolved\": \"https://registry.npmjs.org/tapable/-/tapable-2.1.1.tgz\",\n          \"integrity\": \"sha512-Wib1S8m2wdpLbmQz0RBEVosIyvb/ykfKXf3ZIDqvWoMg/zTNm6G/tDSuUM61J1kNCDXWJrLHGSFeMhAG+gAGpQ==\",\n          \"dev\": true\n        }\n      }\n    },\n    \"enquirer\": {\n      \"version\": \"2.3.6\",\n      \"resolved\": \"https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz\",\n      \"integrity\": \"sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"ansi-colors\": \"^4.1.1\"\n      }\n    },\n    \"entities\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/entities/-/entities-2.2.0.tgz\",\n      \"integrity\": \"sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==\",\n      \"dev\": true\n    },\n    \"envinfo\": {\n      \"version\": \"7.7.3\",\n      \"resolved\": \"https://registry.npmjs.org/envinfo/-/envinfo-7.7.3.tgz\",\n      \"integrity\": \"sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA==\",\n      \"dev\": true\n    },\n    \"error-ex\": {\n      \"version\": \"1.3.2\",\n      \"resolved\": \"https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz\",\n      \"integrity\": \"sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-arrayish\": \"^0.2.1\"\n      }\n    },\n    \"es-abstract\": {\n      \"version\": \"1.18.0-next.1\",\n      \"resolved\": \"https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz\",\n      \"integrity\": \"sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"es-to-primitive\": \"^1.2.1\",\n        \"function-bind\": \"^1.1.1\",\n        \"has\": \"^1.0.3\",\n        \"has-symbols\": \"^1.0.1\",\n        \"is-callable\": \"^1.2.2\",\n        \"is-negative-zero\": \"^2.0.0\",\n        \"is-regex\": \"^1.1.1\",\n        \"object-inspect\": \"^1.8.0\",\n        \"object-keys\": \"^1.1.1\",\n        \"object.assign\": \"^4.1.1\",\n        \"string.prototype.trimend\": \"^1.0.1\",\n        \"string.prototype.trimstart\": \"^1.0.1\"\n      }\n    },\n    \"es-to-primitive\": {\n      \"version\": \"1.2.1\",\n      \"resolved\": \"https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz\",\n      \"integrity\": \"sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-callable\": \"^1.1.4\",\n        \"is-date-object\": \"^1.0.1\",\n        \"is-symbol\": \"^1.0.2\"\n      }\n    },\n    \"escalade\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz\",\n      \"integrity\": \"sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==\",\n      \"dev\": true\n    },\n    \"escape-string-regexp\": {\n      \"version\": \"1.0.5\",\n      \"resolved\": \"https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz\",\n      \"integrity\": \"sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=\",\n      \"dev\": true\n    },\n    \"eslint\": {\n      \"version\": \"7.14.0\",\n      \"resolved\": \"https://registry.npmjs.org/eslint/-/eslint-7.14.0.tgz\",\n      \"integrity\": \"sha512-5YubdnPXrlrYAFCKybPuHIAH++PINe1pmKNc5wQRB9HSbqIK1ywAnntE3Wwua4giKu0bjligf1gLF6qxMGOYRA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/code-frame\": \"^7.0.0\",\n        \"@eslint/eslintrc\": \"^0.2.1\",\n        \"ajv\": \"^6.10.0\",\n        \"chalk\": \"^4.0.0\",\n        \"cross-spawn\": \"^7.0.2\",\n        \"debug\": \"^4.0.1\",\n        \"doctrine\": \"^3.0.0\",\n        \"enquirer\": \"^2.3.5\",\n        \"eslint-scope\": \"^5.1.1\",\n        \"eslint-utils\": \"^2.1.0\",\n        \"eslint-visitor-keys\": \"^2.0.0\",\n        \"espree\": \"^7.3.0\",\n        \"esquery\": \"^1.2.0\",\n        \"esutils\": \"^2.0.2\",\n        \"file-entry-cache\": \"^5.0.1\",\n        \"functional-red-black-tree\": \"^1.0.1\",\n        \"glob-parent\": \"^5.0.0\",\n        \"globals\": \"^12.1.0\",\n        \"ignore\": \"^4.0.6\",\n        \"import-fresh\": \"^3.0.0\",\n        \"imurmurhash\": \"^0.1.4\",\n        \"is-glob\": \"^4.0.0\",\n        \"js-yaml\": \"^3.13.1\",\n        \"json-stable-stringify-without-jsonify\": \"^1.0.1\",\n        \"levn\": \"^0.4.1\",\n        \"lodash\": \"^4.17.19\",\n        \"minimatch\": \"^3.0.4\",\n        \"natural-compare\": \"^1.4.0\",\n        \"optionator\": \"^0.9.1\",\n        \"progress\": \"^2.0.0\",\n        \"regexpp\": \"^3.1.0\",\n        \"semver\": \"^7.2.1\",\n        \"strip-ansi\": \"^6.0.0\",\n        \"strip-json-comments\": \"^3.1.0\",\n        \"table\": \"^5.2.3\",\n        \"text-table\": \"^0.2.0\",\n        \"v8-compile-cache\": \"^2.0.3\"\n      },\n      \"dependencies\": {\n        \"ansi-styles\": {\n          \"version\": \"4.3.0\",\n          \"resolved\": \"https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz\",\n          \"integrity\": \"sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==\",\n          \"dev\": true,\n          \"requires\": {\n            \"color-convert\": \"^2.0.1\"\n          }\n        },\n        \"chalk\": {\n          \"version\": \"4.1.0\",\n          \"resolved\": \"https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz\",\n          \"integrity\": \"sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==\",\n          \"dev\": true,\n          \"requires\": {\n            \"ansi-styles\": \"^4.1.0\",\n            \"supports-color\": \"^7.1.0\"\n          }\n        },\n        \"color-convert\": {\n          \"version\": \"2.0.1\",\n          \"resolved\": \"https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz\",\n          \"integrity\": \"sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==\",\n          \"dev\": true,\n          \"requires\": {\n            \"color-name\": \"~1.1.4\"\n          }\n        },\n        \"color-name\": {\n          \"version\": \"1.1.4\",\n          \"resolved\": \"https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz\",\n          \"integrity\": \"sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==\",\n          \"dev\": true\n        },\n        \"eslint-scope\": {\n          \"version\": \"5.1.1\",\n          \"resolved\": \"https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz\",\n          \"integrity\": \"sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==\",\n          \"dev\": true,\n          \"requires\": {\n            \"esrecurse\": \"^4.3.0\",\n            \"estraverse\": \"^4.1.1\"\n          }\n        },\n        \"eslint-visitor-keys\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz\",\n          \"integrity\": \"sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==\",\n          \"dev\": true\n        },\n        \"globals\": {\n          \"version\": \"12.4.0\",\n          \"resolved\": \"https://registry.npmjs.org/globals/-/globals-12.4.0.tgz\",\n          \"integrity\": \"sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==\",\n          \"dev\": true,\n          \"requires\": {\n            \"type-fest\": \"^0.8.1\"\n          }\n        },\n        \"has-flag\": {\n          \"version\": \"4.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz\",\n          \"integrity\": \"sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==\",\n          \"dev\": true\n        },\n        \"semver\": {\n          \"version\": \"7.3.2\",\n          \"resolved\": \"https://registry.npmjs.org/semver/-/semver-7.3.2.tgz\",\n          \"integrity\": \"sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==\",\n          \"dev\": true\n        },\n        \"supports-color\": {\n          \"version\": \"7.2.0\",\n          \"resolved\": \"https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz\",\n          \"integrity\": \"sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==\",\n          \"dev\": true,\n          \"requires\": {\n            \"has-flag\": \"^4.0.0\"\n          }\n        }\n      }\n    },\n    \"eslint-import-resolver-node\": {\n      \"version\": \"0.3.4\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz\",\n      \"integrity\": \"sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"debug\": \"^2.6.9\",\n        \"resolve\": \"^1.13.1\"\n      },\n      \"dependencies\": {\n        \"debug\": {\n          \"version\": \"2.6.9\",\n          \"resolved\": \"https://registry.npmjs.org/debug/-/debug-2.6.9.tgz\",\n          \"integrity\": \"sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==\",\n          \"dev\": true,\n          \"requires\": {\n            \"ms\": \"2.0.0\"\n          }\n        },\n        \"ms\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/ms/-/ms-2.0.0.tgz\",\n          \"integrity\": \"sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"eslint-module-utils\": {\n      \"version\": \"2.6.0\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz\",\n      \"integrity\": \"sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"debug\": \"^2.6.9\",\n        \"pkg-dir\": \"^2.0.0\"\n      },\n      \"dependencies\": {\n        \"debug\": {\n          \"version\": \"2.6.9\",\n          \"resolved\": \"https://registry.npmjs.org/debug/-/debug-2.6.9.tgz\",\n          \"integrity\": \"sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==\",\n          \"dev\": true,\n          \"requires\": {\n            \"ms\": \"2.0.0\"\n          }\n        },\n        \"ms\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/ms/-/ms-2.0.0.tgz\",\n          \"integrity\": \"sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"eslint-plugin-import\": {\n      \"version\": \"2.22.1\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz\",\n      \"integrity\": \"sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"array-includes\": \"^3.1.1\",\n        \"array.prototype.flat\": \"^1.2.3\",\n        \"contains-path\": \"^0.1.0\",\n        \"debug\": \"^2.6.9\",\n        \"doctrine\": \"1.5.0\",\n        \"eslint-import-resolver-node\": \"^0.3.4\",\n        \"eslint-module-utils\": \"^2.6.0\",\n        \"has\": \"^1.0.3\",\n        \"minimatch\": \"^3.0.4\",\n        \"object.values\": \"^1.1.1\",\n        \"read-pkg-up\": \"^2.0.0\",\n        \"resolve\": \"^1.17.0\",\n        \"tsconfig-paths\": \"^3.9.0\"\n      },\n      \"dependencies\": {\n        \"debug\": {\n          \"version\": \"2.6.9\",\n          \"resolved\": \"https://registry.npmjs.org/debug/-/debug-2.6.9.tgz\",\n          \"integrity\": \"sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==\",\n          \"dev\": true,\n          \"requires\": {\n            \"ms\": \"2.0.0\"\n          }\n        },\n        \"doctrine\": {\n          \"version\": \"1.5.0\",\n          \"resolved\": \"https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz\",\n          \"integrity\": \"sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=\",\n          \"dev\": true,\n          \"requires\": {\n            \"esutils\": \"^2.0.2\",\n            \"isarray\": \"^1.0.0\"\n          }\n        },\n        \"ms\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/ms/-/ms-2.0.0.tgz\",\n          \"integrity\": \"sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"eslint-plugin-jquery\": {\n      \"version\": \"1.5.1\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-plugin-jquery/-/eslint-plugin-jquery-1.5.1.tgz\",\n      \"integrity\": \"sha512-L7v1eaK5t80C0lvUXPFP9MKnBOqPSKhCOYyzy4LZ0+iK+TJwN8S9gAkzzP1AOhypRIwA88HF6phQ9C7jnOpW8w==\",\n      \"dev\": true,\n      \"requires\": {}\n    },\n    \"eslint-plugin-jsdoc\": {\n      \"version\": \"30.7.8\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-30.7.8.tgz\",\n      \"integrity\": \"sha512-OWm2AYvXjCl7nRbpcw5xisfSVkpVAyp4lGqL9T+DeK4kaPm6ecnmTc/G5s1PtcRrwbaI8bIWGzwScqv5CdGyxA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"comment-parser\": \"^0.7.6\",\n        \"debug\": \"^4.2.0\",\n        \"jsdoctypeparser\": \"^9.0.0\",\n        \"lodash\": \"^4.17.20\",\n        \"regextras\": \"^0.7.1\",\n        \"semver\": \"^7.3.2\",\n        \"spdx-expression-parse\": \"^3.0.1\"\n      },\n      \"dependencies\": {\n        \"semver\": {\n          \"version\": \"7.3.2\",\n          \"resolved\": \"https://registry.npmjs.org/semver/-/semver-7.3.2.tgz\",\n          \"integrity\": \"sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==\",\n          \"dev\": true\n        }\n      }\n    },\n    \"eslint-rule-composer\": {\n      \"version\": \"0.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz\",\n      \"integrity\": \"sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==\",\n      \"dev\": true\n    },\n    \"eslint-scope\": {\n      \"version\": \"5.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz\",\n      \"integrity\": \"sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==\",\n      \"dev\": true,\n      \"requires\": {\n        \"esrecurse\": \"^4.1.0\",\n        \"estraverse\": \"^4.1.1\"\n      }\n    },\n    \"eslint-utils\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz\",\n      \"integrity\": \"sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"eslint-visitor-keys\": \"^1.1.0\"\n      }\n    },\n    \"eslint-visitor-keys\": {\n      \"version\": \"1.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz\",\n      \"integrity\": \"sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==\",\n      \"dev\": true\n    },\n    \"espree\": {\n      \"version\": \"7.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/espree/-/espree-7.3.0.tgz\",\n      \"integrity\": \"sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"acorn\": \"^7.4.0\",\n        \"acorn-jsx\": \"^5.2.0\",\n        \"eslint-visitor-keys\": \"^1.3.0\"\n      }\n    },\n    \"esprima\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz\",\n      \"integrity\": \"sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==\",\n      \"dev\": true\n    },\n    \"esquery\": {\n      \"version\": \"1.3.1\",\n      \"resolved\": \"https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz\",\n      \"integrity\": \"sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"estraverse\": \"^5.1.0\"\n      },\n      \"dependencies\": {\n        \"estraverse\": {\n          \"version\": \"5.2.0\",\n          \"resolved\": \"https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz\",\n          \"integrity\": \"sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==\",\n          \"dev\": true\n        }\n      }\n    },\n    \"esrecurse\": {\n      \"version\": \"4.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz\",\n      \"integrity\": \"sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==\",\n      \"dev\": true,\n      \"requires\": {\n        \"estraverse\": \"^5.2.0\"\n      },\n      \"dependencies\": {\n        \"estraverse\": {\n          \"version\": \"5.2.0\",\n          \"resolved\": \"https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz\",\n          \"integrity\": \"sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==\",\n          \"dev\": true\n        }\n      }\n    },\n    \"estraverse\": {\n      \"version\": \"4.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz\",\n      \"integrity\": \"sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==\",\n      \"dev\": true\n    },\n    \"esutils\": {\n      \"version\": \"2.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz\",\n      \"integrity\": \"sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==\",\n      \"dev\": true\n    },\n    \"events\": {\n      \"version\": \"3.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/events/-/events-3.2.0.tgz\",\n      \"integrity\": \"sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==\",\n      \"dev\": true\n    },\n    \"execa\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/execa/-/execa-4.1.0.tgz\",\n      \"integrity\": \"sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"cross-spawn\": \"^7.0.0\",\n        \"get-stream\": \"^5.0.0\",\n        \"human-signals\": \"^1.1.1\",\n        \"is-stream\": \"^2.0.0\",\n        \"merge-stream\": \"^2.0.0\",\n        \"npm-run-path\": \"^4.0.0\",\n        \"onetime\": \"^5.1.0\",\n        \"signal-exit\": \"^3.0.2\",\n        \"strip-final-newline\": \"^2.0.0\"\n      }\n    },\n    \"fast-deep-equal\": {\n      \"version\": \"3.1.3\",\n      \"resolved\": \"https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz\",\n      \"integrity\": \"sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==\",\n      \"dev\": true\n    },\n    \"fast-json-stable-stringify\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz\",\n      \"integrity\": \"sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==\",\n      \"dev\": true\n    },\n    \"fast-levenshtein\": {\n      \"version\": \"2.0.6\",\n      \"resolved\": \"https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz\",\n      \"integrity\": \"sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=\",\n      \"dev\": true\n    },\n    \"file-entry-cache\": {\n      \"version\": \"5.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz\",\n      \"integrity\": \"sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==\",\n      \"dev\": true,\n      \"requires\": {\n        \"flat-cache\": \"^2.0.1\"\n      }\n    },\n    \"fill-range\": {\n      \"version\": \"7.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz\",\n      \"integrity\": \"sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"to-regex-range\": \"^5.0.1\"\n      }\n    },\n    \"find-cache-dir\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz\",\n      \"integrity\": \"sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"commondir\": \"^1.0.1\",\n        \"make-dir\": \"^2.0.0\",\n        \"pkg-dir\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"find-up\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz\",\n          \"integrity\": \"sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==\",\n          \"dev\": true,\n          \"requires\": {\n            \"locate-path\": \"^3.0.0\"\n          }\n        },\n        \"locate-path\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz\",\n          \"integrity\": \"sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-locate\": \"^3.0.0\",\n            \"path-exists\": \"^3.0.0\"\n          }\n        },\n        \"p-limit\": {\n          \"version\": \"2.3.0\",\n          \"resolved\": \"https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz\",\n          \"integrity\": \"sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-try\": \"^2.0.0\"\n          }\n        },\n        \"p-locate\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz\",\n          \"integrity\": \"sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-limit\": \"^2.0.0\"\n          }\n        },\n        \"p-try\": {\n          \"version\": \"2.2.0\",\n          \"resolved\": \"https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz\",\n          \"integrity\": \"sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==\",\n          \"dev\": true\n        },\n        \"pkg-dir\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz\",\n          \"integrity\": \"sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==\",\n          \"dev\": true,\n          \"requires\": {\n            \"find-up\": \"^3.0.0\"\n          }\n        }\n      }\n    },\n    \"find-up\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz\",\n      \"integrity\": \"sha1-RdG35QbHF93UgndaK3eSCjwMV6c=\",\n      \"dev\": true,\n      \"requires\": {\n        \"locate-path\": \"^2.0.0\"\n      }\n    },\n    \"flat-cache\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz\",\n      \"integrity\": \"sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"flatted\": \"^2.0.0\",\n        \"rimraf\": \"2.6.3\",\n        \"write\": \"1.0.3\"\n      }\n    },\n    \"flatted\": {\n      \"version\": \"2.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz\",\n      \"integrity\": \"sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==\",\n      \"dev\": true\n    },\n    \"fs.realpath\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz\",\n      \"integrity\": \"sha1-FQStJSMVjKpA20onh8sBQRmU6k8=\",\n      \"dev\": true\n    },\n    \"fsevents\": {\n      \"version\": \"2.1.3\",\n      \"resolved\": \"https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz\",\n      \"integrity\": \"sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==\",\n      \"dev\": true,\n      \"optional\": true\n    },\n    \"function-bind\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz\",\n      \"integrity\": \"sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==\",\n      \"dev\": true\n    },\n    \"functional-red-black-tree\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz\",\n      \"integrity\": \"sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=\",\n      \"dev\": true\n    },\n    \"gensync\": {\n      \"version\": \"1.0.0-beta.2\",\n      \"resolved\": \"https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz\",\n      \"integrity\": \"sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==\",\n      \"dev\": true\n    },\n    \"get-intrinsic\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz\",\n      \"integrity\": \"sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"function-bind\": \"^1.1.1\",\n        \"has\": \"^1.0.3\",\n        \"has-symbols\": \"^1.0.1\"\n      }\n    },\n    \"get-stream\": {\n      \"version\": \"5.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz\",\n      \"integrity\": \"sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"pump\": \"^3.0.0\"\n      }\n    },\n    \"glob\": {\n      \"version\": \"7.1.6\",\n      \"resolved\": \"https://registry.npmjs.org/glob/-/glob-7.1.6.tgz\",\n      \"integrity\": \"sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"fs.realpath\": \"^1.0.0\",\n        \"inflight\": \"^1.0.4\",\n        \"inherits\": \"2\",\n        \"minimatch\": \"^3.0.4\",\n        \"once\": \"^1.3.0\",\n        \"path-is-absolute\": \"^1.0.0\"\n      }\n    },\n    \"glob-parent\": {\n      \"version\": \"5.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz\",\n      \"integrity\": \"sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-glob\": \"^4.0.1\"\n      }\n    },\n    \"glob-to-regexp\": {\n      \"version\": \"0.4.1\",\n      \"resolved\": \"https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz\",\n      \"integrity\": \"sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==\",\n      \"dev\": true\n    },\n    \"globals\": {\n      \"version\": \"11.12.0\",\n      \"resolved\": \"https://registry.npmjs.org/globals/-/globals-11.12.0.tgz\",\n      \"integrity\": \"sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==\",\n      \"dev\": true\n    },\n    \"graceful-fs\": {\n      \"version\": \"4.2.4\",\n      \"resolved\": \"https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz\",\n      \"integrity\": \"sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==\",\n      \"dev\": true\n    },\n    \"has\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/has/-/has-1.0.3.tgz\",\n      \"integrity\": \"sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"function-bind\": \"^1.1.1\"\n      }\n    },\n    \"has-flag\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz\",\n      \"integrity\": \"sha1-tdRU3CGZriJWmfNGfloH87lVuv0=\",\n      \"dev\": true\n    },\n    \"has-symbols\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz\",\n      \"integrity\": \"sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==\",\n      \"dev\": true\n    },\n    \"he\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/he/-/he-1.2.0.tgz\",\n      \"integrity\": \"sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==\",\n      \"dev\": true\n    },\n    \"hosted-git-info\": {\n      \"version\": \"2.8.9\",\n      \"resolved\": \"https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz\",\n      \"integrity\": \"sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==\",\n      \"dev\": true\n    },\n    \"html-minifier-terser\": {\n      \"version\": \"5.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz\",\n      \"integrity\": \"sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"camel-case\": \"^4.1.1\",\n        \"clean-css\": \"^4.2.3\",\n        \"commander\": \"^4.1.1\",\n        \"he\": \"^1.2.0\",\n        \"param-case\": \"^3.0.3\",\n        \"relateurl\": \"^0.2.7\",\n        \"terser\": \"^4.6.3\"\n      }\n    },\n    \"html-webpack-plugin\": {\n      \"version\": \"4.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz\",\n      \"integrity\": \"sha512-MouoXEYSjTzCrjIxWwg8gxL5fE2X2WZJLmBYXlaJhQUH5K/b5OrqmV7T4dB7iu0xkmJ6JlUuV6fFVtnqbPopZw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@types/html-minifier-terser\": \"^5.0.0\",\n        \"@types/tapable\": \"^1.0.5\",\n        \"@types/webpack\": \"^4.41.8\",\n        \"html-minifier-terser\": \"^5.0.1\",\n        \"loader-utils\": \"^1.2.3\",\n        \"lodash\": \"^4.17.15\",\n        \"pretty-error\": \"^2.1.1\",\n        \"tapable\": \"^1.1.3\",\n        \"util.promisify\": \"1.0.0\"\n      }\n    },\n    \"htmlparser2\": {\n      \"version\": \"6.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz\",\n      \"integrity\": \"sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==\",\n      \"dev\": true,\n      \"requires\": {\n        \"domelementtype\": \"^2.0.1\",\n        \"domhandler\": \"^4.0.0\",\n        \"domutils\": \"^2.5.2\",\n        \"entities\": \"^2.0.0\"\n      }\n    },\n    \"human-signals\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz\",\n      \"integrity\": \"sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==\",\n      \"dev\": true\n    },\n    \"icss-utils\": {\n      \"version\": \"5.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz\",\n      \"integrity\": \"sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==\",\n      \"dev\": true,\n      \"requires\": {}\n    },\n    \"ignore\": {\n      \"version\": \"4.0.6\",\n      \"resolved\": \"https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz\",\n      \"integrity\": \"sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==\",\n      \"dev\": true\n    },\n    \"import-fresh\": {\n      \"version\": \"3.2.2\",\n      \"resolved\": \"https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz\",\n      \"integrity\": \"sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"parent-module\": \"^1.0.0\",\n        \"resolve-from\": \"^4.0.0\"\n      }\n    },\n    \"import-local\": {\n      \"version\": \"3.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz\",\n      \"integrity\": \"sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"pkg-dir\": \"^4.2.0\",\n        \"resolve-cwd\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"find-up\": {\n          \"version\": \"4.1.0\",\n          \"resolved\": \"https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz\",\n          \"integrity\": \"sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==\",\n          \"dev\": true,\n          \"requires\": {\n            \"locate-path\": \"^5.0.0\",\n            \"path-exists\": \"^4.0.0\"\n          }\n        },\n        \"locate-path\": {\n          \"version\": \"5.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz\",\n          \"integrity\": \"sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-locate\": \"^4.1.0\"\n          }\n        },\n        \"p-limit\": {\n          \"version\": \"2.3.0\",\n          \"resolved\": \"https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz\",\n          \"integrity\": \"sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-try\": \"^2.0.0\"\n          }\n        },\n        \"p-locate\": {\n          \"version\": \"4.1.0\",\n          \"resolved\": \"https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz\",\n          \"integrity\": \"sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-limit\": \"^2.2.0\"\n          }\n        },\n        \"p-try\": {\n          \"version\": \"2.2.0\",\n          \"resolved\": \"https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz\",\n          \"integrity\": \"sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==\",\n          \"dev\": true\n        },\n        \"path-exists\": {\n          \"version\": \"4.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz\",\n          \"integrity\": \"sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==\",\n          \"dev\": true\n        },\n        \"pkg-dir\": {\n          \"version\": \"4.2.0\",\n          \"resolved\": \"https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz\",\n          \"integrity\": \"sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==\",\n          \"dev\": true,\n          \"requires\": {\n            \"find-up\": \"^4.0.0\"\n          }\n        }\n      }\n    },\n    \"imurmurhash\": {\n      \"version\": \"0.1.4\",\n      \"resolved\": \"https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz\",\n      \"integrity\": \"sha1-khi5srkoojixPcT7a21XbyMUU+o=\",\n      \"dev\": true\n    },\n    \"indexes-of\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz\",\n      \"integrity\": \"sha1-8w9xbI4r00bHtn0985FVZqfAVgc=\",\n      \"dev\": true\n    },\n    \"inflight\": {\n      \"version\": \"1.0.6\",\n      \"resolved\": \"https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz\",\n      \"integrity\": \"sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=\",\n      \"dev\": true,\n      \"requires\": {\n        \"once\": \"^1.3.0\",\n        \"wrappy\": \"1\"\n      }\n    },\n    \"inherits\": {\n      \"version\": \"2.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz\",\n      \"integrity\": \"sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==\",\n      \"dev\": true\n    },\n    \"interpret\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz\",\n      \"integrity\": \"sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==\",\n      \"dev\": true\n    },\n    \"is-arrayish\": {\n      \"version\": \"0.2.1\",\n      \"resolved\": \"https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz\",\n      \"integrity\": \"sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=\",\n      \"dev\": true\n    },\n    \"is-binary-path\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz\",\n      \"integrity\": \"sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"binary-extensions\": \"^2.0.0\"\n      }\n    },\n    \"is-callable\": {\n      \"version\": \"1.2.2\",\n      \"resolved\": \"https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz\",\n      \"integrity\": \"sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==\",\n      \"dev\": true\n    },\n    \"is-core-module\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/is-core-module/-/is-core-module-2.1.0.tgz\",\n      \"integrity\": \"sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"has\": \"^1.0.3\"\n      }\n    },\n    \"is-date-object\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz\",\n      \"integrity\": \"sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==\",\n      \"dev\": true\n    },\n    \"is-extglob\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz\",\n      \"integrity\": \"sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=\",\n      \"dev\": true\n    },\n    \"is-fullwidth-code-point\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz\",\n      \"integrity\": \"sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=\",\n      \"dev\": true\n    },\n    \"is-glob\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz\",\n      \"integrity\": \"sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-extglob\": \"^2.1.1\"\n      }\n    },\n    \"is-negative-zero\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz\",\n      \"integrity\": \"sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=\",\n      \"dev\": true\n    },\n    \"is-number\": {\n      \"version\": \"7.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz\",\n      \"integrity\": \"sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==\",\n      \"dev\": true\n    },\n    \"is-regex\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz\",\n      \"integrity\": \"sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"has-symbols\": \"^1.0.1\"\n      }\n    },\n    \"is-stream\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz\",\n      \"integrity\": \"sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==\",\n      \"dev\": true\n    },\n    \"is-string\": {\n      \"version\": \"1.0.5\",\n      \"resolved\": \"https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz\",\n      \"integrity\": \"sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==\",\n      \"dev\": true\n    },\n    \"is-symbol\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz\",\n      \"integrity\": \"sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"has-symbols\": \"^1.0.1\"\n      }\n    },\n    \"isarray\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz\",\n      \"integrity\": \"sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=\",\n      \"dev\": true\n    },\n    \"isexe\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz\",\n      \"integrity\": \"sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=\",\n      \"dev\": true\n    },\n    \"jest-worker\": {\n      \"version\": \"26.6.2\",\n      \"resolved\": \"https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz\",\n      \"integrity\": \"sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@types/node\": \"*\",\n        \"merge-stream\": \"^2.0.0\",\n        \"supports-color\": \"^7.0.0\"\n      },\n      \"dependencies\": {\n        \"has-flag\": {\n          \"version\": \"4.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz\",\n          \"integrity\": \"sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==\",\n          \"dev\": true\n        },\n        \"supports-color\": {\n          \"version\": \"7.2.0\",\n          \"resolved\": \"https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz\",\n          \"integrity\": \"sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==\",\n          \"dev\": true,\n          \"requires\": {\n            \"has-flag\": \"^4.0.0\"\n          }\n        }\n      }\n    },\n    \"jquery\": {\n      \"version\": \"3.5.1\",\n      \"resolved\": \"https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz\",\n      \"integrity\": \"sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==\"\n    },\n    \"jquery-migrate\": {\n      \"version\": \"3.3.2\",\n      \"resolved\": \"https://registry.npmjs.org/jquery-migrate/-/jquery-migrate-3.3.2.tgz\",\n      \"integrity\": \"sha512-L3gYhr7yEtLUSAeqXSicVa0vRD4aGwjw/bWY8YzrO2o/qDY1BaMyP3oB3bZf5Auy3Hu9ynliio0CTyDWCBPVDw==\",\n      \"requires\": {}\n    },\n    \"js-tokens\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz\",\n      \"integrity\": \"sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==\",\n      \"dev\": true\n    },\n    \"js-yaml\": {\n      \"version\": \"3.14.0\",\n      \"resolved\": \"https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz\",\n      \"integrity\": \"sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==\",\n      \"dev\": true,\n      \"requires\": {\n        \"argparse\": \"^1.0.7\",\n        \"esprima\": \"^4.0.0\"\n      }\n    },\n    \"jsdoctypeparser\": {\n      \"version\": \"9.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-9.0.0.tgz\",\n      \"integrity\": \"sha512-jrTA2jJIL6/DAEILBEh2/w9QxCuwmvNXIry39Ay/HVfhE3o2yVV0U44blYkqdHA/OKloJEqvJy0xU+GSdE2SIw==\",\n      \"dev\": true\n    },\n    \"jsesc\": {\n      \"version\": \"2.5.2\",\n      \"resolved\": \"https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz\",\n      \"integrity\": \"sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==\",\n      \"dev\": true\n    },\n    \"json-parse-better-errors\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz\",\n      \"integrity\": \"sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==\",\n      \"dev\": true\n    },\n    \"json-parse-even-better-errors\": {\n      \"version\": \"2.3.1\",\n      \"resolved\": \"https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz\",\n      \"integrity\": \"sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==\",\n      \"dev\": true\n    },\n    \"json-schema-traverse\": {\n      \"version\": \"0.4.1\",\n      \"resolved\": \"https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz\",\n      \"integrity\": \"sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==\",\n      \"dev\": true\n    },\n    \"json-stable-stringify-without-jsonify\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz\",\n      \"integrity\": \"sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=\",\n      \"dev\": true\n    },\n    \"json5\": {\n      \"version\": \"2.2.3\",\n      \"resolved\": \"https://registry.npmjs.org/json5/-/json5-2.2.3.tgz\",\n      \"integrity\": \"sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==\",\n      \"dev\": true\n    },\n    \"klona\": {\n      \"version\": \"2.0.6\",\n      \"resolved\": \"https://registry.npmjs.org/klona/-/klona-2.0.6.tgz\",\n      \"integrity\": \"sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==\",\n      \"dev\": true\n    },\n    \"leven\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/leven/-/leven-3.1.0.tgz\",\n      \"integrity\": \"sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==\",\n      \"dev\": true\n    },\n    \"levn\": {\n      \"version\": \"0.4.1\",\n      \"resolved\": \"https://registry.npmjs.org/levn/-/levn-0.4.1.tgz\",\n      \"integrity\": \"sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"prelude-ls\": \"^1.2.1\",\n        \"type-check\": \"~0.4.0\"\n      }\n    },\n    \"lines-and-columns\": {\n      \"version\": \"1.2.4\",\n      \"resolved\": \"https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz\",\n      \"integrity\": \"sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==\",\n      \"dev\": true\n    },\n    \"load-json-file\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz\",\n      \"integrity\": \"sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"graceful-fs\": \"^4.1.2\",\n        \"parse-json\": \"^2.2.0\",\n        \"pify\": \"^2.0.0\",\n        \"strip-bom\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"parse-json\": {\n          \"version\": \"2.2.0\",\n          \"resolved\": \"https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz\",\n          \"integrity\": \"sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=\",\n          \"dev\": true,\n          \"requires\": {\n            \"error-ex\": \"^1.2.0\"\n          }\n        },\n        \"pify\": {\n          \"version\": \"2.3.0\",\n          \"resolved\": \"https://registry.npmjs.org/pify/-/pify-2.3.0.tgz\",\n          \"integrity\": \"sha1-7RQaasBDqEnqWISY59yosVMw6Qw=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"loader-runner\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/loader-runner/-/loader-runner-4.1.0.tgz\",\n      \"integrity\": \"sha512-oR4lB4WvwFoC70ocraKhn5nkKSs23t57h9udUgw8o0iH8hMXeEoRuUgfcvgUwAJ1ZpRqBvcou4N2SMvM1DwMrA==\",\n      \"dev\": true\n    },\n    \"loader-utils\": {\n      \"version\": \"1.4.2\",\n      \"resolved\": \"https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz\",\n      \"integrity\": \"sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"big.js\": \"^5.2.2\",\n        \"emojis-list\": \"^3.0.0\",\n        \"json5\": \"^1.0.1\"\n      },\n      \"dependencies\": {\n        \"json5\": {\n          \"version\": \"1.0.2\",\n          \"resolved\": \"https://registry.npmjs.org/json5/-/json5-1.0.2.tgz\",\n          \"integrity\": \"sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==\",\n          \"dev\": true,\n          \"requires\": {\n            \"minimist\": \"^1.2.0\"\n          }\n        }\n      }\n    },\n    \"locate-path\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz\",\n      \"integrity\": \"sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"p-locate\": \"^2.0.0\",\n        \"path-exists\": \"^3.0.0\"\n      }\n    },\n    \"lodash\": {\n      \"version\": \"4.17.21\",\n      \"resolved\": \"https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz\",\n      \"integrity\": \"sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==\",\n      \"dev\": true\n    },\n    \"lower-case\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz\",\n      \"integrity\": \"sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"tslib\": \"^1.10.0\"\n      }\n    },\n    \"lru-cache\": {\n      \"version\": \"6.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz\",\n      \"integrity\": \"sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"yallist\": \"^4.0.0\"\n      }\n    },\n    \"make-dir\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz\",\n      \"integrity\": \"sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"pify\": \"^4.0.1\",\n        \"semver\": \"^5.6.0\"\n      }\n    },\n    \"make-error\": {\n      \"version\": \"1.3.6\",\n      \"resolved\": \"https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz\",\n      \"integrity\": \"sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"merge-stream\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz\",\n      \"integrity\": \"sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==\",\n      \"dev\": true\n    },\n    \"mime-db\": {\n      \"version\": \"1.44.0\",\n      \"resolved\": \"https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz\",\n      \"integrity\": \"sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==\",\n      \"dev\": true\n    },\n    \"mime-types\": {\n      \"version\": \"2.1.27\",\n      \"resolved\": \"https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz\",\n      \"integrity\": \"sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==\",\n      \"dev\": true,\n      \"requires\": {\n        \"mime-db\": \"1.44.0\"\n      }\n    },\n    \"mimic-fn\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz\",\n      \"integrity\": \"sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==\",\n      \"dev\": true\n    },\n    \"mini-css-extract-plugin\": {\n      \"version\": \"1.3.1\",\n      \"resolved\": \"https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.1.tgz\",\n      \"integrity\": \"sha512-jIOheqh9EU98rqj6ZaFTYNNDSFqdakNqaUZfkYwaXPjI9batmXVXX+K71NrqRAgtoGefELBMld1EQ7dqSAD5SQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"loader-utils\": \"^2.0.0\",\n        \"schema-utils\": \"^3.0.0\",\n        \"webpack-sources\": \"^1.1.0\"\n      },\n      \"dependencies\": {\n        \"loader-utils\": {\n          \"version\": \"2.0.4\",\n          \"resolved\": \"https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz\",\n          \"integrity\": \"sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==\",\n          \"dev\": true,\n          \"requires\": {\n            \"big.js\": \"^5.2.2\",\n            \"emojis-list\": \"^3.0.0\",\n            \"json5\": \"^2.1.2\"\n          }\n        },\n        \"schema-utils\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz\",\n          \"integrity\": \"sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==\",\n          \"dev\": true,\n          \"requires\": {\n            \"@types/json-schema\": \"^7.0.6\",\n            \"ajv\": \"^6.12.5\",\n            \"ajv-keywords\": \"^3.5.2\"\n          }\n        }\n      }\n    },\n    \"minimatch\": {\n      \"version\": \"3.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz\",\n      \"integrity\": \"sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"brace-expansion\": \"^1.1.7\"\n      }\n    },\n    \"minimist\": {\n      \"version\": \"1.2.6\",\n      \"resolved\": \"https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz\",\n      \"integrity\": \"sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==\",\n      \"dev\": true\n    },\n    \"mkdirp\": {\n      \"version\": \"0.5.5\",\n      \"resolved\": \"https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz\",\n      \"integrity\": \"sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"minimist\": \"^1.2.5\"\n      }\n    },\n    \"ms\": {\n      \"version\": \"2.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/ms/-/ms-2.1.2.tgz\",\n      \"integrity\": \"sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==\",\n      \"dev\": true\n    },\n    \"nanoid\": {\n      \"version\": \"3.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz\",\n      \"integrity\": \"sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==\",\n      \"dev\": true\n    },\n    \"natural-compare\": {\n      \"version\": \"1.4.0\",\n      \"resolved\": \"https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz\",\n      \"integrity\": \"sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=\",\n      \"dev\": true\n    },\n    \"neo-async\": {\n      \"version\": \"2.6.2\",\n      \"resolved\": \"https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz\",\n      \"integrity\": \"sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==\",\n      \"dev\": true\n    },\n    \"no-case\": {\n      \"version\": \"3.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/no-case/-/no-case-3.0.3.tgz\",\n      \"integrity\": \"sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"lower-case\": \"^2.0.1\",\n        \"tslib\": \"^1.10.0\"\n      }\n    },\n    \"node-releases\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz\",\n      \"integrity\": \"sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==\",\n      \"dev\": true\n    },\n    \"normalize-package-data\": {\n      \"version\": \"2.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz\",\n      \"integrity\": \"sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"hosted-git-info\": \"^2.1.4\",\n        \"resolve\": \"^1.10.0\",\n        \"semver\": \"2 || 3 || 4 || 5\",\n        \"validate-npm-package-license\": \"^3.0.1\"\n      }\n    },\n    \"normalize-path\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz\",\n      \"integrity\": \"sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==\",\n      \"dev\": true\n    },\n    \"normalize-range\": {\n      \"version\": \"0.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz\",\n      \"integrity\": \"sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=\",\n      \"dev\": true\n    },\n    \"npm-run-path\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz\",\n      \"integrity\": \"sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"path-key\": \"^3.0.0\"\n      }\n    },\n    \"nth-check\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz\",\n      \"integrity\": \"sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==\",\n      \"dev\": true,\n      \"requires\": {\n        \"boolbase\": \"^1.0.0\"\n      }\n    },\n    \"num2fraction\": {\n      \"version\": \"1.2.2\",\n      \"resolved\": \"https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz\",\n      \"integrity\": \"sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=\",\n      \"dev\": true\n    },\n    \"object-inspect\": {\n      \"version\": \"1.8.0\",\n      \"resolved\": \"https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz\",\n      \"integrity\": \"sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==\",\n      \"dev\": true\n    },\n    \"object-keys\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz\",\n      \"integrity\": \"sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==\",\n      \"dev\": true\n    },\n    \"object.assign\": {\n      \"version\": \"4.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz\",\n      \"integrity\": \"sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"call-bind\": \"^1.0.0\",\n        \"define-properties\": \"^1.1.3\",\n        \"has-symbols\": \"^1.0.1\",\n        \"object-keys\": \"^1.1.1\"\n      }\n    },\n    \"object.getownpropertydescriptors\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz\",\n      \"integrity\": \"sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"define-properties\": \"^1.1.3\",\n        \"es-abstract\": \"^1.17.0-next.1\"\n      },\n      \"dependencies\": {\n        \"es-abstract\": {\n          \"version\": \"1.17.7\",\n          \"resolved\": \"https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz\",\n          \"integrity\": \"sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==\",\n          \"dev\": true,\n          \"requires\": {\n            \"es-to-primitive\": \"^1.2.1\",\n            \"function-bind\": \"^1.1.1\",\n            \"has\": \"^1.0.3\",\n            \"has-symbols\": \"^1.0.1\",\n            \"is-callable\": \"^1.2.2\",\n            \"is-regex\": \"^1.1.1\",\n            \"object-inspect\": \"^1.8.0\",\n            \"object-keys\": \"^1.1.1\",\n            \"object.assign\": \"^4.1.1\",\n            \"string.prototype.trimend\": \"^1.0.1\",\n            \"string.prototype.trimstart\": \"^1.0.1\"\n          }\n        }\n      }\n    },\n    \"object.values\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz\",\n      \"integrity\": \"sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"define-properties\": \"^1.1.3\",\n        \"es-abstract\": \"^1.17.0-next.1\",\n        \"function-bind\": \"^1.1.1\",\n        \"has\": \"^1.0.3\"\n      },\n      \"dependencies\": {\n        \"es-abstract\": {\n          \"version\": \"1.17.7\",\n          \"resolved\": \"https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz\",\n          \"integrity\": \"sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==\",\n          \"dev\": true,\n          \"requires\": {\n            \"es-to-primitive\": \"^1.2.1\",\n            \"function-bind\": \"^1.1.1\",\n            \"has\": \"^1.0.3\",\n            \"has-symbols\": \"^1.0.1\",\n            \"is-callable\": \"^1.2.2\",\n            \"is-regex\": \"^1.1.1\",\n            \"object-inspect\": \"^1.8.0\",\n            \"object-keys\": \"^1.1.1\",\n            \"object.assign\": \"^4.1.1\",\n            \"string.prototype.trimend\": \"^1.0.1\",\n            \"string.prototype.trimstart\": \"^1.0.1\"\n          }\n        }\n      }\n    },\n    \"once\": {\n      \"version\": \"1.4.0\",\n      \"resolved\": \"https://registry.npmjs.org/once/-/once-1.4.0.tgz\",\n      \"integrity\": \"sha1-WDsap3WWHUsROsF9nFC6753Xa9E=\",\n      \"dev\": true,\n      \"requires\": {\n        \"wrappy\": \"1\"\n      }\n    },\n    \"onetime\": {\n      \"version\": \"5.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz\",\n      \"integrity\": \"sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"mimic-fn\": \"^2.1.0\"\n      }\n    },\n    \"optionator\": {\n      \"version\": \"0.9.1\",\n      \"resolved\": \"https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz\",\n      \"integrity\": \"sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"deep-is\": \"^0.1.3\",\n        \"fast-levenshtein\": \"^2.0.6\",\n        \"levn\": \"^0.4.1\",\n        \"prelude-ls\": \"^1.2.1\",\n        \"type-check\": \"^0.4.0\",\n        \"word-wrap\": \"^1.2.3\"\n      }\n    },\n    \"p-limit\": {\n      \"version\": \"1.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz\",\n      \"integrity\": \"sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==\",\n      \"dev\": true,\n      \"requires\": {\n        \"p-try\": \"^1.0.0\"\n      }\n    },\n    \"p-locate\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz\",\n      \"integrity\": \"sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=\",\n      \"dev\": true,\n      \"requires\": {\n        \"p-limit\": \"^1.1.0\"\n      }\n    },\n    \"p-try\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz\",\n      \"integrity\": \"sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=\",\n      \"dev\": true\n    },\n    \"param-case\": {\n      \"version\": \"3.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/param-case/-/param-case-3.0.3.tgz\",\n      \"integrity\": \"sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"dot-case\": \"^3.0.3\",\n        \"tslib\": \"^1.10.0\"\n      }\n    },\n    \"parent-module\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz\",\n      \"integrity\": \"sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==\",\n      \"dev\": true,\n      \"requires\": {\n        \"callsites\": \"^3.0.0\"\n      }\n    },\n    \"parse-json\": {\n      \"version\": \"5.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz\",\n      \"integrity\": \"sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/code-frame\": \"^7.0.0\",\n        \"error-ex\": \"^1.3.1\",\n        \"json-parse-even-better-errors\": \"^2.3.0\",\n        \"lines-and-columns\": \"^1.1.6\"\n      }\n    },\n    \"pascal-case\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.1.tgz\",\n      \"integrity\": \"sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"no-case\": \"^3.0.3\",\n        \"tslib\": \"^1.10.0\"\n      }\n    },\n    \"path-exists\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz\",\n      \"integrity\": \"sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=\",\n      \"dev\": true\n    },\n    \"path-is-absolute\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz\",\n      \"integrity\": \"sha1-F0uSaHNVNP+8es5r9TpanhtcX18=\",\n      \"dev\": true\n    },\n    \"path-key\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz\",\n      \"integrity\": \"sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==\",\n      \"dev\": true\n    },\n    \"path-parse\": {\n      \"version\": \"1.0.7\",\n      \"resolved\": \"https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz\",\n      \"integrity\": \"sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==\",\n      \"dev\": true\n    },\n    \"path-type\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz\",\n      \"integrity\": \"sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==\",\n      \"dev\": true\n    },\n    \"picocolors\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz\",\n      \"integrity\": \"sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==\",\n      \"dev\": true\n    },\n    \"picomatch\": {\n      \"version\": \"2.2.2\",\n      \"resolved\": \"https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz\",\n      \"integrity\": \"sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==\",\n      \"dev\": true\n    },\n    \"pify\": {\n      \"version\": \"4.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/pify/-/pify-4.0.1.tgz\",\n      \"integrity\": \"sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==\",\n      \"dev\": true\n    },\n    \"pkg-dir\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz\",\n      \"integrity\": \"sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=\",\n      \"dev\": true,\n      \"requires\": {\n        \"find-up\": \"^2.1.0\"\n      }\n    },\n    \"popper.js\": {\n      \"version\": \"1.16.1\",\n      \"resolved\": \"https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz\",\n      \"integrity\": \"sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==\"\n    },\n    \"postcss\": {\n      \"version\": \"8.2.15\",\n      \"resolved\": \"https://registry.npmjs.org/postcss/-/postcss-8.2.15.tgz\",\n      \"integrity\": \"sha512-2zO3b26eJD/8rb106Qu2o7Qgg52ND5HPjcyQiK2B98O388h43A448LCslC0dI2P97wCAQRJsFvwTRcXxTKds+Q==\",\n      \"dev\": true,\n      \"requires\": {\n        \"colorette\": \"^1.2.2\",\n        \"nanoid\": \"^3.1.23\",\n        \"source-map\": \"^0.6.1\"\n      },\n      \"dependencies\": {\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==\",\n          \"dev\": true\n        }\n      }\n    },\n    \"postcss-loader\": {\n      \"version\": \"7.2.4\",\n      \"resolved\": \"https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.2.4.tgz\",\n      \"integrity\": \"sha512-F88rpxxNspo5hatIc+orYwZDtHFaVFOSIVAx+fBfJC1GmhWbVmPWtmg2gXKE1OxJbneOSGn8PWdIwsZFcruS+w==\",\n      \"dev\": true,\n      \"requires\": {\n        \"cosmiconfig\": \"^8.1.3\",\n        \"cosmiconfig-typescript-loader\": \"^4.3.0\",\n        \"klona\": \"^2.0.6\",\n        \"semver\": \"^7.3.8\"\n      },\n      \"dependencies\": {\n        \"semver\": {\n          \"version\": \"7.5.0\",\n          \"resolved\": \"https://registry.npmjs.org/semver/-/semver-7.5.0.tgz\",\n          \"integrity\": \"sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==\",\n          \"dev\": true,\n          \"requires\": {\n            \"lru-cache\": \"^6.0.0\"\n          }\n        }\n      }\n    },\n    \"postcss-modules-extract-imports\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz\",\n      \"integrity\": \"sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==\",\n      \"dev\": true,\n      \"requires\": {}\n    },\n    \"postcss-modules-local-by-default\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz\",\n      \"integrity\": \"sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"icss-utils\": \"^5.0.0\",\n        \"postcss-selector-parser\": \"^6.0.2\",\n        \"postcss-value-parser\": \"^4.1.0\"\n      }\n    },\n    \"postcss-modules-scope\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz\",\n      \"integrity\": \"sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"postcss-selector-parser\": \"^6.0.4\"\n      }\n    },\n    \"postcss-modules-values\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz\",\n      \"integrity\": \"sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"icss-utils\": \"^5.0.0\"\n      }\n    },\n    \"postcss-selector-parser\": {\n      \"version\": \"6.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz\",\n      \"integrity\": \"sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"cssesc\": \"^3.0.0\",\n        \"indexes-of\": \"^1.0.1\",\n        \"uniq\": \"^1.0.1\",\n        \"util-deprecate\": \"^1.0.2\"\n      }\n    },\n    \"postcss-value-parser\": {\n      \"version\": \"4.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz\",\n      \"integrity\": \"sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==\",\n      \"dev\": true\n    },\n    \"prelude-ls\": {\n      \"version\": \"1.2.1\",\n      \"resolved\": \"https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz\",\n      \"integrity\": \"sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==\",\n      \"dev\": true\n    },\n    \"pretty-error\": {\n      \"version\": \"2.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz\",\n      \"integrity\": \"sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"lodash\": \"^4.17.20\",\n        \"renderkid\": \"^2.0.4\"\n      }\n    },\n    \"progress\": {\n      \"version\": \"2.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/progress/-/progress-2.0.3.tgz\",\n      \"integrity\": \"sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==\",\n      \"dev\": true\n    },\n    \"pump\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/pump/-/pump-3.0.0.tgz\",\n      \"integrity\": \"sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==\",\n      \"dev\": true,\n      \"requires\": {\n        \"end-of-stream\": \"^1.1.0\",\n        \"once\": \"^1.3.1\"\n      }\n    },\n    \"punycode\": {\n      \"version\": \"2.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz\",\n      \"integrity\": \"sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==\",\n      \"dev\": true\n    },\n    \"randombytes\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz\",\n      \"integrity\": \"sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"safe-buffer\": \"^5.1.0\"\n      }\n    },\n    \"read-pkg\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz\",\n      \"integrity\": \"sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=\",\n      \"dev\": true,\n      \"requires\": {\n        \"load-json-file\": \"^2.0.0\",\n        \"normalize-package-data\": \"^2.3.2\",\n        \"path-type\": \"^2.0.0\"\n      },\n      \"dependencies\": {\n        \"path-type\": {\n          \"version\": \"2.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz\",\n          \"integrity\": \"sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=\",\n          \"dev\": true,\n          \"requires\": {\n            \"pify\": \"^2.0.0\"\n          }\n        },\n        \"pify\": {\n          \"version\": \"2.3.0\",\n          \"resolved\": \"https://registry.npmjs.org/pify/-/pify-2.3.0.tgz\",\n          \"integrity\": \"sha1-7RQaasBDqEnqWISY59yosVMw6Qw=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"read-pkg-up\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz\",\n      \"integrity\": \"sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=\",\n      \"dev\": true,\n      \"requires\": {\n        \"find-up\": \"^2.0.0\",\n        \"read-pkg\": \"^2.0.0\"\n      }\n    },\n    \"readdirp\": {\n      \"version\": \"3.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz\",\n      \"integrity\": \"sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"picomatch\": \"^2.2.1\"\n      }\n    },\n    \"rechoir\": {\n      \"version\": \"0.7.0\",\n      \"resolved\": \"https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz\",\n      \"integrity\": \"sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==\",\n      \"dev\": true,\n      \"requires\": {\n        \"resolve\": \"^1.9.0\"\n      }\n    },\n    \"reduce-flatten\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz\",\n      \"integrity\": \"sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==\",\n      \"dev\": true\n    },\n    \"regenerate\": {\n      \"version\": \"1.4.2\",\n      \"resolved\": \"https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz\",\n      \"integrity\": \"sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==\",\n      \"dev\": true\n    },\n    \"regenerate-unicode-properties\": {\n      \"version\": \"8.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz\",\n      \"integrity\": \"sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"regenerate\": \"^1.4.0\"\n      }\n    },\n    \"regenerator-runtime\": {\n      \"version\": \"0.13.7\",\n      \"resolved\": \"https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz\",\n      \"integrity\": \"sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==\",\n      \"dev\": true\n    },\n    \"regenerator-transform\": {\n      \"version\": \"0.14.5\",\n      \"resolved\": \"https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz\",\n      \"integrity\": \"sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@babel/runtime\": \"^7.8.4\"\n      }\n    },\n    \"regexpp\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz\",\n      \"integrity\": \"sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==\",\n      \"dev\": true\n    },\n    \"regexpu-core\": {\n      \"version\": \"4.7.1\",\n      \"resolved\": \"https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz\",\n      \"integrity\": \"sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"regenerate\": \"^1.4.0\",\n        \"regenerate-unicode-properties\": \"^8.2.0\",\n        \"regjsgen\": \"^0.5.1\",\n        \"regjsparser\": \"^0.6.4\",\n        \"unicode-match-property-ecmascript\": \"^1.0.4\",\n        \"unicode-match-property-value-ecmascript\": \"^1.2.0\"\n      }\n    },\n    \"regextras\": {\n      \"version\": \"0.7.1\",\n      \"resolved\": \"https://registry.npmjs.org/regextras/-/regextras-0.7.1.tgz\",\n      \"integrity\": \"sha512-9YXf6xtW+qzQ+hcMQXx95MOvfqXFgsKDZodX3qZB0x2n5Z94ioetIITsBtvJbiOyxa/6s9AtyweBLCdPmPko/w==\",\n      \"dev\": true\n    },\n    \"regjsgen\": {\n      \"version\": \"0.5.2\",\n      \"resolved\": \"https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz\",\n      \"integrity\": \"sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==\",\n      \"dev\": true\n    },\n    \"regjsparser\": {\n      \"version\": \"0.6.4\",\n      \"resolved\": \"https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz\",\n      \"integrity\": \"sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"jsesc\": \"~0.5.0\"\n      },\n      \"dependencies\": {\n        \"jsesc\": {\n          \"version\": \"0.5.0\",\n          \"resolved\": \"https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz\",\n          \"integrity\": \"sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=\",\n          \"dev\": true\n        }\n      }\n    },\n    \"relateurl\": {\n      \"version\": \"0.2.7\",\n      \"resolved\": \"https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz\",\n      \"integrity\": \"sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=\",\n      \"dev\": true\n    },\n    \"renderkid\": {\n      \"version\": \"2.0.7\",\n      \"resolved\": \"https://registry.npmjs.org/renderkid/-/renderkid-2.0.7.tgz\",\n      \"integrity\": \"sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"css-select\": \"^4.1.3\",\n        \"dom-converter\": \"^0.2.0\",\n        \"htmlparser2\": \"^6.1.0\",\n        \"lodash\": \"^4.17.21\",\n        \"strip-ansi\": \"^3.0.1\"\n      },\n      \"dependencies\": {\n        \"ansi-regex\": {\n          \"version\": \"2.1.1\",\n          \"resolved\": \"https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz\",\n          \"integrity\": \"sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==\",\n          \"dev\": true\n        },\n        \"strip-ansi\": {\n          \"version\": \"3.0.1\",\n          \"resolved\": \"https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz\",\n          \"integrity\": \"sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=\",\n          \"dev\": true,\n          \"requires\": {\n            \"ansi-regex\": \"^2.0.0\"\n          }\n        }\n      }\n    },\n    \"resolve\": {\n      \"version\": \"1.19.0\",\n      \"resolved\": \"https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz\",\n      \"integrity\": \"sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-core-module\": \"^2.1.0\",\n        \"path-parse\": \"^1.0.6\"\n      }\n    },\n    \"resolve-cwd\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz\",\n      \"integrity\": \"sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"resolve-from\": \"^5.0.0\"\n      },\n      \"dependencies\": {\n        \"resolve-from\": {\n          \"version\": \"5.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz\",\n          \"integrity\": \"sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==\",\n          \"dev\": true\n        }\n      }\n    },\n    \"resolve-from\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz\",\n      \"integrity\": \"sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==\",\n      \"dev\": true\n    },\n    \"rimraf\": {\n      \"version\": \"2.6.3\",\n      \"resolved\": \"https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz\",\n      \"integrity\": \"sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"glob\": \"^7.1.3\"\n      }\n    },\n    \"safe-buffer\": {\n      \"version\": \"5.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz\",\n      \"integrity\": \"sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==\",\n      \"dev\": true\n    },\n    \"sass\": {\n      \"version\": \"1.29.0\",\n      \"resolved\": \"https://registry.npmjs.org/sass/-/sass-1.29.0.tgz\",\n      \"integrity\": \"sha512-ZpwAUFgnvAUCdkjwPREny+17BpUj8nh5Yr6zKPGtLNTLrmtoRYIjm7njP24COhjJldjwW1dcv52Lpf4tNZVVRA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"chokidar\": \">=2.0.0 <4.0.0\"\n      }\n    },\n    \"sass-loader\": {\n      \"version\": \"10.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/sass-loader/-/sass-loader-10.1.0.tgz\",\n      \"integrity\": \"sha512-ZCKAlczLBbFd3aGAhowpYEy69Te3Z68cg8bnHHl6WnSCvnKpbM6pQrz957HWMa8LKVuhnD9uMplmMAHwGQtHeg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"klona\": \"^2.0.4\",\n        \"loader-utils\": \"^2.0.0\",\n        \"neo-async\": \"^2.6.2\",\n        \"schema-utils\": \"^3.0.0\",\n        \"semver\": \"^7.3.2\"\n      },\n      \"dependencies\": {\n        \"loader-utils\": {\n          \"version\": \"2.0.4\",\n          \"resolved\": \"https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz\",\n          \"integrity\": \"sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==\",\n          \"dev\": true,\n          \"requires\": {\n            \"big.js\": \"^5.2.2\",\n            \"emojis-list\": \"^3.0.0\",\n            \"json5\": \"^2.1.2\"\n          }\n        },\n        \"schema-utils\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz\",\n          \"integrity\": \"sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==\",\n          \"dev\": true,\n          \"requires\": {\n            \"@types/json-schema\": \"^7.0.6\",\n            \"ajv\": \"^6.12.5\",\n            \"ajv-keywords\": \"^3.5.2\"\n          }\n        },\n        \"semver\": {\n          \"version\": \"7.3.2\",\n          \"resolved\": \"https://registry.npmjs.org/semver/-/semver-7.3.2.tgz\",\n          \"integrity\": \"sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==\",\n          \"dev\": true\n        }\n      }\n    },\n    \"schema-utils\": {\n      \"version\": \"2.7.1\",\n      \"resolved\": \"https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz\",\n      \"integrity\": \"sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@types/json-schema\": \"^7.0.5\",\n        \"ajv\": \"^6.12.4\",\n        \"ajv-keywords\": \"^3.5.2\"\n      }\n    },\n    \"semver\": {\n      \"version\": \"5.7.1\",\n      \"resolved\": \"https://registry.npmjs.org/semver/-/semver-5.7.1.tgz\",\n      \"integrity\": \"sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==\",\n      \"dev\": true\n    },\n    \"serialize-javascript\": {\n      \"version\": \"5.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz\",\n      \"integrity\": \"sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"randombytes\": \"^2.1.0\"\n      }\n    },\n    \"shebang-command\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz\",\n      \"integrity\": \"sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"shebang-regex\": \"^3.0.0\"\n      }\n    },\n    \"shebang-regex\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz\",\n      \"integrity\": \"sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==\",\n      \"dev\": true\n    },\n    \"signal-exit\": {\n      \"version\": \"3.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz\",\n      \"integrity\": \"sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==\",\n      \"dev\": true\n    },\n    \"slice-ansi\": {\n      \"version\": \"2.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz\",\n      \"integrity\": \"sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"ansi-styles\": \"^3.2.0\",\n        \"astral-regex\": \"^1.0.0\",\n        \"is-fullwidth-code-point\": \"^2.0.0\"\n      }\n    },\n    \"source-list-map\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz\",\n      \"integrity\": \"sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==\",\n      \"dev\": true\n    },\n    \"source-map\": {\n      \"version\": \"0.5.7\",\n      \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz\",\n      \"integrity\": \"sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=\",\n      \"dev\": true\n    },\n    \"source-map-support\": {\n      \"version\": \"0.5.21\",\n      \"resolved\": \"https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz\",\n      \"integrity\": \"sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==\",\n      \"dev\": true,\n      \"requires\": {\n        \"buffer-from\": \"^1.0.0\",\n        \"source-map\": \"^0.6.0\"\n      },\n      \"dependencies\": {\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==\",\n          \"dev\": true\n        }\n      }\n    },\n    \"spdx-correct\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz\",\n      \"integrity\": \"sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==\",\n      \"dev\": true,\n      \"requires\": {\n        \"spdx-expression-parse\": \"^3.0.0\",\n        \"spdx-license-ids\": \"^3.0.0\"\n      }\n    },\n    \"spdx-exceptions\": {\n      \"version\": \"2.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz\",\n      \"integrity\": \"sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==\",\n      \"dev\": true\n    },\n    \"spdx-expression-parse\": {\n      \"version\": \"3.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz\",\n      \"integrity\": \"sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==\",\n      \"dev\": true,\n      \"requires\": {\n        \"spdx-exceptions\": \"^2.1.0\",\n        \"spdx-license-ids\": \"^3.0.0\"\n      }\n    },\n    \"spdx-license-ids\": {\n      \"version\": \"3.0.6\",\n      \"resolved\": \"https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz\",\n      \"integrity\": \"sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==\",\n      \"dev\": true\n    },\n    \"sprintf-js\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz\",\n      \"integrity\": \"sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=\",\n      \"dev\": true\n    },\n    \"string-width\": {\n      \"version\": \"3.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz\",\n      \"integrity\": \"sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==\",\n      \"dev\": true,\n      \"requires\": {\n        \"emoji-regex\": \"^7.0.1\",\n        \"is-fullwidth-code-point\": \"^2.0.0\",\n        \"strip-ansi\": \"^5.1.0\"\n      },\n      \"dependencies\": {\n        \"ansi-regex\": {\n          \"version\": \"4.1.1\",\n          \"resolved\": \"https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz\",\n          \"integrity\": \"sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==\",\n          \"dev\": true\n        },\n        \"strip-ansi\": {\n          \"version\": \"5.2.0\",\n          \"resolved\": \"https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz\",\n          \"integrity\": \"sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==\",\n          \"dev\": true,\n          \"requires\": {\n            \"ansi-regex\": \"^4.1.0\"\n          }\n        }\n      }\n    },\n    \"string.prototype.trimend\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz\",\n      \"integrity\": \"sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"call-bind\": \"^1.0.0\",\n        \"define-properties\": \"^1.1.3\"\n      }\n    },\n    \"string.prototype.trimstart\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz\",\n      \"integrity\": \"sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"call-bind\": \"^1.0.0\",\n        \"define-properties\": \"^1.1.3\"\n      }\n    },\n    \"strip-ansi\": {\n      \"version\": \"6.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz\",\n      \"integrity\": \"sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==\",\n      \"dev\": true,\n      \"requires\": {\n        \"ansi-regex\": \"^5.0.0\"\n      }\n    },\n    \"strip-bom\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz\",\n      \"integrity\": \"sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=\",\n      \"dev\": true\n    },\n    \"strip-final-newline\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz\",\n      \"integrity\": \"sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==\",\n      \"dev\": true\n    },\n    \"strip-json-comments\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz\",\n      \"integrity\": \"sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==\",\n      \"dev\": true\n    },\n    \"supports-color\": {\n      \"version\": \"5.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz\",\n      \"integrity\": \"sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==\",\n      \"dev\": true,\n      \"requires\": {\n        \"has-flag\": \"^3.0.0\"\n      }\n    },\n    \"table\": {\n      \"version\": \"5.4.6\",\n      \"resolved\": \"https://registry.npmjs.org/table/-/table-5.4.6.tgz\",\n      \"integrity\": \"sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==\",\n      \"dev\": true,\n      \"requires\": {\n        \"ajv\": \"^6.10.2\",\n        \"lodash\": \"^4.17.14\",\n        \"slice-ansi\": \"^2.1.0\",\n        \"string-width\": \"^3.0.0\"\n      }\n    },\n    \"table-layout\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/table-layout/-/table-layout-1.0.1.tgz\",\n      \"integrity\": \"sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q==\",\n      \"dev\": true,\n      \"requires\": {\n        \"array-back\": \"^4.0.1\",\n        \"deep-extend\": \"~0.6.0\",\n        \"typical\": \"^5.2.0\",\n        \"wordwrapjs\": \"^4.0.0\"\n      }\n    },\n    \"tapable\": {\n      \"version\": \"1.1.3\",\n      \"resolved\": \"https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz\",\n      \"integrity\": \"sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==\",\n      \"dev\": true\n    },\n    \"terser\": {\n      \"version\": \"4.8.1\",\n      \"resolved\": \"https://registry.npmjs.org/terser/-/terser-4.8.1.tgz\",\n      \"integrity\": \"sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"commander\": \"^2.20.0\",\n        \"source-map\": \"~0.6.1\",\n        \"source-map-support\": \"~0.5.12\"\n      },\n      \"dependencies\": {\n        \"commander\": {\n          \"version\": \"2.20.3\",\n          \"resolved\": \"https://registry.npmjs.org/commander/-/commander-2.20.3.tgz\",\n          \"integrity\": \"sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==\",\n          \"dev\": true\n        },\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==\",\n          \"dev\": true\n        }\n      }\n    },\n    \"terser-webpack-plugin\": {\n      \"version\": \"5.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.0.3.tgz\",\n      \"integrity\": \"sha512-zFdGk8Lh9ZJGPxxPE6jwysOlATWB8GMW8HcfGULWA/nPal+3VdATflQvSBSLQJRCmYZnfFJl6vkRTiwJGNgPiQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"jest-worker\": \"^26.6.1\",\n        \"p-limit\": \"^3.0.2\",\n        \"schema-utils\": \"^3.0.0\",\n        \"serialize-javascript\": \"^5.0.1\",\n        \"source-map\": \"^0.6.1\",\n        \"terser\": \"^5.3.8\"\n      },\n      \"dependencies\": {\n        \"acorn\": {\n          \"version\": \"8.7.1\",\n          \"resolved\": \"https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz\",\n          \"integrity\": \"sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==\",\n          \"dev\": true\n        },\n        \"commander\": {\n          \"version\": \"2.20.3\",\n          \"resolved\": \"https://registry.npmjs.org/commander/-/commander-2.20.3.tgz\",\n          \"integrity\": \"sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==\",\n          \"dev\": true\n        },\n        \"p-limit\": {\n          \"version\": \"3.0.2\",\n          \"resolved\": \"https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz\",\n          \"integrity\": \"sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-try\": \"^2.0.0\"\n          }\n        },\n        \"p-try\": {\n          \"version\": \"2.2.0\",\n          \"resolved\": \"https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz\",\n          \"integrity\": \"sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==\",\n          \"dev\": true\n        },\n        \"schema-utils\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz\",\n          \"integrity\": \"sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==\",\n          \"dev\": true,\n          \"requires\": {\n            \"@types/json-schema\": \"^7.0.6\",\n            \"ajv\": \"^6.12.5\",\n            \"ajv-keywords\": \"^3.5.2\"\n          }\n        },\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==\",\n          \"dev\": true\n        },\n        \"terser\": {\n          \"version\": \"5.14.2\",\n          \"resolved\": \"https://registry.npmjs.org/terser/-/terser-5.14.2.tgz\",\n          \"integrity\": \"sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==\",\n          \"dev\": true,\n          \"requires\": {\n            \"@jridgewell/source-map\": \"^0.3.2\",\n            \"acorn\": \"^8.5.0\",\n            \"commander\": \"^2.20.0\",\n            \"source-map-support\": \"~0.5.20\"\n          }\n        }\n      }\n    },\n    \"text-table\": {\n      \"version\": \"0.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz\",\n      \"integrity\": \"sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=\",\n      \"dev\": true\n    },\n    \"to-fast-properties\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz\",\n      \"integrity\": \"sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=\",\n      \"dev\": true\n    },\n    \"to-regex-range\": {\n      \"version\": \"5.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz\",\n      \"integrity\": \"sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"is-number\": \"^7.0.0\"\n      }\n    },\n    \"ts-node\": {\n      \"version\": \"10.9.1\",\n      \"resolved\": \"https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz\",\n      \"integrity\": \"sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==\",\n      \"dev\": true,\n      \"peer\": true,\n      \"requires\": {\n        \"@cspotcode/source-map-support\": \"^0.8.0\",\n        \"@tsconfig/node10\": \"^1.0.7\",\n        \"@tsconfig/node12\": \"^1.0.7\",\n        \"@tsconfig/node14\": \"^1.0.0\",\n        \"@tsconfig/node16\": \"^1.0.2\",\n        \"acorn\": \"^8.4.1\",\n        \"acorn-walk\": \"^8.1.1\",\n        \"arg\": \"^4.1.0\",\n        \"create-require\": \"^1.1.0\",\n        \"diff\": \"^4.0.1\",\n        \"make-error\": \"^1.1.1\",\n        \"v8-compile-cache-lib\": \"^3.0.1\",\n        \"yn\": \"3.1.1\"\n      },\n      \"dependencies\": {\n        \"acorn\": {\n          \"version\": \"8.8.2\",\n          \"resolved\": \"https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz\",\n          \"integrity\": \"sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==\",\n          \"dev\": true,\n          \"peer\": true\n        }\n      }\n    },\n    \"tsconfig-paths\": {\n      \"version\": \"3.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz\",\n      \"integrity\": \"sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@types/json5\": \"^0.0.29\",\n        \"json5\": \"^1.0.1\",\n        \"minimist\": \"^1.2.0\",\n        \"strip-bom\": \"^3.0.0\"\n      },\n      \"dependencies\": {\n        \"json5\": {\n          \"version\": \"1.0.2\",\n          \"resolved\": \"https://registry.npmjs.org/json5/-/json5-1.0.2.tgz\",\n          \"integrity\": \"sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==\",\n          \"dev\": true,\n          \"requires\": {\n            \"minimist\": \"^1.2.0\"\n          }\n        }\n      }\n    },\n    \"tslib\": {\n      \"version\": \"1.14.1\",\n      \"resolved\": \"https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz\",\n      \"integrity\": \"sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==\",\n      \"dev\": true\n    },\n    \"type-check\": {\n      \"version\": \"0.4.0\",\n      \"resolved\": \"https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz\",\n      \"integrity\": \"sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==\",\n      \"dev\": true,\n      \"requires\": {\n        \"prelude-ls\": \"^1.2.1\"\n      }\n    },\n    \"type-fest\": {\n      \"version\": \"0.8.1\",\n      \"resolved\": \"https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz\",\n      \"integrity\": \"sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==\",\n      \"dev\": true\n    },\n    \"typescript\": {\n      \"version\": \"5.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz\",\n      \"integrity\": \"sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"typical\": {\n      \"version\": \"5.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/typical/-/typical-5.2.0.tgz\",\n      \"integrity\": \"sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==\",\n      \"dev\": true\n    },\n    \"unicode-canonical-property-names-ecmascript\": {\n      \"version\": \"1.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz\",\n      \"integrity\": \"sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==\",\n      \"dev\": true\n    },\n    \"unicode-match-property-ecmascript\": {\n      \"version\": \"1.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz\",\n      \"integrity\": \"sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"unicode-canonical-property-names-ecmascript\": \"^1.0.4\",\n        \"unicode-property-aliases-ecmascript\": \"^1.0.4\"\n      }\n    },\n    \"unicode-match-property-value-ecmascript\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz\",\n      \"integrity\": \"sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==\",\n      \"dev\": true\n    },\n    \"unicode-property-aliases-ecmascript\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz\",\n      \"integrity\": \"sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==\",\n      \"dev\": true\n    },\n    \"uniq\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz\",\n      \"integrity\": \"sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=\",\n      \"dev\": true\n    },\n    \"uri-js\": {\n      \"version\": \"4.4.0\",\n      \"resolved\": \"https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz\",\n      \"integrity\": \"sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==\",\n      \"dev\": true,\n      \"requires\": {\n        \"punycode\": \"^2.1.0\"\n      }\n    },\n    \"util-deprecate\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz\",\n      \"integrity\": \"sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=\",\n      \"dev\": true\n    },\n    \"util.promisify\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz\",\n      \"integrity\": \"sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"define-properties\": \"^1.1.2\",\n        \"object.getownpropertydescriptors\": \"^2.0.3\"\n      }\n    },\n    \"utila\": {\n      \"version\": \"0.4.0\",\n      \"resolved\": \"https://registry.npmjs.org/utila/-/utila-0.4.0.tgz\",\n      \"integrity\": \"sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=\",\n      \"dev\": true\n    },\n    \"v8-compile-cache\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz\",\n      \"integrity\": \"sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==\",\n      \"dev\": true\n    },\n    \"v8-compile-cache-lib\": {\n      \"version\": \"3.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz\",\n      \"integrity\": \"sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==\",\n      \"dev\": true,\n      \"peer\": true\n    },\n    \"validate-npm-package-license\": {\n      \"version\": \"3.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz\",\n      \"integrity\": \"sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==\",\n      \"dev\": true,\n      \"requires\": {\n        \"spdx-correct\": \"^3.0.0\",\n        \"spdx-expression-parse\": \"^3.0.0\"\n      }\n    },\n    \"watchpack\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/watchpack/-/watchpack-2.0.1.tgz\",\n      \"integrity\": \"sha512-vO8AKGX22ZRo6PiOFM9dC0re8IcKh8Kd/aH2zeqUc6w4/jBGlTy2P7fTC6ekT0NjVeGjgU2dGC5rNstKkeLEQg==\",\n      \"dev\": true,\n      \"requires\": {\n        \"glob-to-regexp\": \"^0.4.1\",\n        \"graceful-fs\": \"^4.1.2\"\n      }\n    },\n    \"webpack\": {\n      \"version\": \"5.6.0\",\n      \"resolved\": \"https://registry.npmjs.org/webpack/-/webpack-5.6.0.tgz\",\n      \"integrity\": \"sha512-SIeFuBhuheKElRbd84O35UhKc0nxlgSwtzm2ksZ0BVhRJqxVJxEguT/pYhfiR0le/pxTa1VsCp7EOYyTsa6XOA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@types/eslint-scope\": \"^3.7.0\",\n        \"@types/estree\": \"^0.0.45\",\n        \"@webassemblyjs/ast\": \"1.9.0\",\n        \"@webassemblyjs/helper-module-context\": \"1.9.0\",\n        \"@webassemblyjs/wasm-edit\": \"1.9.0\",\n        \"@webassemblyjs/wasm-parser\": \"1.9.0\",\n        \"acorn\": \"^8.0.4\",\n        \"browserslist\": \"^4.14.5\",\n        \"chrome-trace-event\": \"^1.0.2\",\n        \"enhanced-resolve\": \"^5.3.1\",\n        \"eslint-scope\": \"^5.1.1\",\n        \"events\": \"^3.2.0\",\n        \"glob-to-regexp\": \"^0.4.1\",\n        \"graceful-fs\": \"^4.2.4\",\n        \"json-parse-better-errors\": \"^1.0.2\",\n        \"loader-runner\": \"^4.1.0\",\n        \"mime-types\": \"^2.1.27\",\n        \"neo-async\": \"^2.6.2\",\n        \"pkg-dir\": \"^4.2.0\",\n        \"schema-utils\": \"^3.0.0\",\n        \"tapable\": \"^2.0.0\",\n        \"terser-webpack-plugin\": \"^5.0.3\",\n        \"watchpack\": \"^2.0.0\",\n        \"webpack-sources\": \"^2.1.1\"\n      },\n      \"dependencies\": {\n        \"acorn\": {\n          \"version\": \"8.0.4\",\n          \"resolved\": \"https://registry.npmjs.org/acorn/-/acorn-8.0.4.tgz\",\n          \"integrity\": \"sha512-XNP0PqF1XD19ZlLKvB7cMmnZswW4C/03pRHgirB30uSJTaS3A3V1/P4sS3HPvFmjoriPCJQs+JDSbm4bL1TxGQ==\",\n          \"dev\": true\n        },\n        \"eslint-scope\": {\n          \"version\": \"5.1.1\",\n          \"resolved\": \"https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz\",\n          \"integrity\": \"sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==\",\n          \"dev\": true,\n          \"requires\": {\n            \"esrecurse\": \"^4.3.0\",\n            \"estraverse\": \"^4.1.1\"\n          }\n        },\n        \"find-up\": {\n          \"version\": \"4.1.0\",\n          \"resolved\": \"https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz\",\n          \"integrity\": \"sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==\",\n          \"dev\": true,\n          \"requires\": {\n            \"locate-path\": \"^5.0.0\",\n            \"path-exists\": \"^4.0.0\"\n          }\n        },\n        \"locate-path\": {\n          \"version\": \"5.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz\",\n          \"integrity\": \"sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-locate\": \"^4.1.0\"\n          }\n        },\n        \"p-limit\": {\n          \"version\": \"2.3.0\",\n          \"resolved\": \"https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz\",\n          \"integrity\": \"sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-try\": \"^2.0.0\"\n          }\n        },\n        \"p-locate\": {\n          \"version\": \"4.1.0\",\n          \"resolved\": \"https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz\",\n          \"integrity\": \"sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==\",\n          \"dev\": true,\n          \"requires\": {\n            \"p-limit\": \"^2.2.0\"\n          }\n        },\n        \"p-try\": {\n          \"version\": \"2.2.0\",\n          \"resolved\": \"https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz\",\n          \"integrity\": \"sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==\",\n          \"dev\": true\n        },\n        \"path-exists\": {\n          \"version\": \"4.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz\",\n          \"integrity\": \"sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==\",\n          \"dev\": true\n        },\n        \"pkg-dir\": {\n          \"version\": \"4.2.0\",\n          \"resolved\": \"https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz\",\n          \"integrity\": \"sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==\",\n          \"dev\": true,\n          \"requires\": {\n            \"find-up\": \"^4.0.0\"\n          }\n        },\n        \"schema-utils\": {\n          \"version\": \"3.0.0\",\n          \"resolved\": \"https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz\",\n          \"integrity\": \"sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==\",\n          \"dev\": true,\n          \"requires\": {\n            \"@types/json-schema\": \"^7.0.6\",\n            \"ajv\": \"^6.12.5\",\n            \"ajv-keywords\": \"^3.5.2\"\n          }\n        },\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==\",\n          \"dev\": true\n        },\n        \"tapable\": {\n          \"version\": \"2.1.1\",\n          \"resolved\": \"https://registry.npmjs.org/tapable/-/tapable-2.1.1.tgz\",\n          \"integrity\": \"sha512-Wib1S8m2wdpLbmQz0RBEVosIyvb/ykfKXf3ZIDqvWoMg/zTNm6G/tDSuUM61J1kNCDXWJrLHGSFeMhAG+gAGpQ==\",\n          \"dev\": true\n        },\n        \"webpack-sources\": {\n          \"version\": \"2.2.0\",\n          \"resolved\": \"https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.2.0.tgz\",\n          \"integrity\": \"sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w==\",\n          \"dev\": true,\n          \"requires\": {\n            \"source-list-map\": \"^2.0.1\",\n            \"source-map\": \"^0.6.1\"\n          }\n        }\n      }\n    },\n    \"webpack-cli\": {\n      \"version\": \"4.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.2.0.tgz\",\n      \"integrity\": \"sha512-EIl3k88vaF4fSxWSgtAQR+VwicfLMTZ9amQtqS4o+TDPW9HGaEpbFBbAZ4A3ZOT5SOnMxNOzROsSTPiE8tBJPA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"@webpack-cli/info\": \"^1.1.0\",\n        \"@webpack-cli/serve\": \"^1.1.0\",\n        \"colorette\": \"^1.2.1\",\n        \"command-line-usage\": \"^6.1.0\",\n        \"commander\": \"^6.2.0\",\n        \"enquirer\": \"^2.3.6\",\n        \"execa\": \"^4.1.0\",\n        \"import-local\": \"^3.0.2\",\n        \"interpret\": \"^2.2.0\",\n        \"leven\": \"^3.1.0\",\n        \"rechoir\": \"^0.7.0\",\n        \"v8-compile-cache\": \"^2.2.0\",\n        \"webpack-merge\": \"^4.2.2\"\n      },\n      \"dependencies\": {\n        \"commander\": {\n          \"version\": \"6.2.0\",\n          \"resolved\": \"https://registry.npmjs.org/commander/-/commander-6.2.0.tgz\",\n          \"integrity\": \"sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==\",\n          \"dev\": true\n        }\n      }\n    },\n    \"webpack-merge\": {\n      \"version\": \"4.2.2\",\n      \"resolved\": \"https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz\",\n      \"integrity\": \"sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==\",\n      \"dev\": true,\n      \"requires\": {\n        \"lodash\": \"^4.17.15\"\n      }\n    },\n    \"webpack-sources\": {\n      \"version\": \"1.4.3\",\n      \"resolved\": \"https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz\",\n      \"integrity\": \"sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"source-list-map\": \"^2.0.0\",\n        \"source-map\": \"~0.6.1\"\n      },\n      \"dependencies\": {\n        \"source-map\": {\n          \"version\": \"0.6.1\",\n          \"resolved\": \"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\",\n          \"integrity\": \"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==\",\n          \"dev\": true\n        }\n      }\n    },\n    \"which\": {\n      \"version\": \"2.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/which/-/which-2.0.2.tgz\",\n      \"integrity\": \"sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==\",\n      \"dev\": true,\n      \"requires\": {\n        \"isexe\": \"^2.0.0\"\n      }\n    },\n    \"word-wrap\": {\n      \"version\": \"1.2.3\",\n      \"resolved\": \"https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz\",\n      \"integrity\": \"sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==\",\n      \"dev\": true\n    },\n    \"wordwrapjs\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.0.tgz\",\n      \"integrity\": \"sha512-Svqw723a3R34KvsMgpjFBYCgNOSdcW3mQFK4wIfhGQhtaFVOJmdYoXgi63ne3dTlWgatVcUc7t4HtQ/+bUVIzQ==\",\n      \"dev\": true,\n      \"requires\": {\n        \"reduce-flatten\": \"^2.0.0\",\n        \"typical\": \"^5.0.0\"\n      }\n    },\n    \"wrappy\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz\",\n      \"integrity\": \"sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=\",\n      \"dev\": true\n    },\n    \"write\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/write/-/write-1.0.3.tgz\",\n      \"integrity\": \"sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==\",\n      \"dev\": true,\n      \"requires\": {\n        \"mkdirp\": \"^0.5.1\"\n      }\n    },\n    \"yallist\": {\n      \"version\": \"4.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz\",\n      \"integrity\": \"sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==\",\n      \"dev\": true\n    },\n    \"yn\": {\n      \"version\": \"3.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/yn/-/yn-3.1.1.tgz\",\n      \"integrity\": \"sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==\",\n      \"dev\": true,\n      \"peer\": true\n    }\n  }\n}\n"
  },
  {
    "path": "web/package.json",
    "content": "{\n  \"name\": \"botamusique\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"lint\": \"eslint --config .eslintrc.json js/ --ext .mjs\",\n    \"build\": \"webpack --config webpack.config.cjs --progress\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/azlux/botamusique.git\"\n  },\n  \"author\": \"azlux\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/azlux/botamusique/issues\"\n  },\n  \"homepage\": \"https://github.com/azlux/botamusique#readme\",\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.12.9\",\n    \"@babel/eslint-parser\": \"^7.12.1\",\n    \"@babel/eslint-plugin\": \"^7.12.1\",\n    \"@babel/plugin-proposal-class-properties\": \"^7.12.1\",\n    \"@babel/preset-env\": \"^7.12.7\",\n    \"autoprefixer\": \"^10.0.2\",\n    \"babel-loader\": \"^8.2.1\",\n    \"core-js\": \"^3.7.0\",\n    \"css-loader\": \"^5.0.1\",\n    \"eslint\": \"^7.14.0\",\n    \"eslint-plugin-import\": \"^2.22.1\",\n    \"eslint-plugin-jquery\": \"^1.5.1\",\n    \"eslint-plugin-jsdoc\": \"^30.7.8\",\n    \"html-webpack-plugin\": \"^4.5.0\",\n    \"mini-css-extract-plugin\": \"^1.3.1\",\n    \"postcss-loader\": \"^7.2.4\",\n    \"regenerator-runtime\": \"^0.13.7\",\n    \"sass\": \"^1.29.0\",\n    \"sass-loader\": \"^10.1.0\",\n    \"webpack\": \"^5.6.0\",\n    \"webpack-cli\": \"^4.2.0\"\n  },\n  \"dependencies\": {\n    \"@fortawesome/fontawesome-svg-core\": \"^1.2.32\",\n    \"@fortawesome/free-regular-svg-icons\": \"^5.15.1\",\n    \"@fortawesome/free-solid-svg-icons\": \"^5.15.1\",\n    \"bootstrap\": \"^4.5.3\",\n    \"bootswatch\": \"^4.5.3\",\n    \"jquery\": \"^3.5.1\",\n    \"jquery-migrate\": \"^3.3.2\",\n    \"popper.js\": \"^1.16.1\"\n  }\n}\n"
  },
  {
    "path": "web/sass/app-dark.scss",
    "content": "@import '~bootswatch/dist/darkly/variables';\n@import '~bootstrap/scss/bootstrap';\n@import '~bootswatch/dist/darkly/bootswatch';\n\n@import './main';\n"
  },
  {
    "path": "web/sass/app.scss",
    "content": "@import '~bootstrap/scss/bootstrap';\n\n@import './main';\n"
  },
  {
    "path": "web/sass/main.scss",
    "content": ".btn-space {\n    margin-right: 5px;\n}\n\n/* Playlist */\n.playlist-item {\n    transition: all 0.2s ease-in-out;\n}\n\n.playlist-artwork {\n    float: left;\n    margin-left: 10px;\n    white-space: nowrap;\n    overflow: hidden;\n}\n\n.tag-space {\n    margin-right: 3px;\n}\n\n.tag-click {\n    cursor: pointer;\n    transition: 400ms;\n}\n\n.tag-unclicked {\n    opacity: 0.6;\n}\n\n.tag-clicked {\n    box-shadow: 2px 4px 10px #777777;\n    transform: scale(1.2);\n    opacity: 1;\n    margin: 5px;\n}\n\n.library-item {\n    display: flex;\n    padding: .5rem .5rem .5rem 0;\n    height: 72px;\n    transition: ease-in-out 200ms;\n}\n\n.library-thumb-img {\n    width: 70px;\n    height: 70px;\n    border-radius: 5px;\n}\n\n.library-thumb-col {\n    position: relative;\n    padding-left: 0;\n    overflow: hidden;\n    margin: -0.5rem 1rem -0.5rem 0;\n}\n\n.library-thumb-grp {\n    position: absolute;\n    top: 0;\n    left: -95px;\n    width: 70px;\n    margin-left: 15px;\n    transition: left 300ms;\n    border-radius: 5px;\n    opacity: 0.7;\n    font-weight: 300;\n}\n\n.library-thumb-grp-hover {\n    left: -15px;\n}\n\n.library-thumb-btn-up {\n    position: absolute !important;\n    top: 0;\n    height: 70px;\n    font-size: 2em;\n    padding-top: 10px;\n}\n\n.library-btn-svg {\n    width: 1rem;\n    fill: currentColor;\n}\n\n.library-info-col {\n    margin-right: 1rem;\n    padding: 3px 0;\n    display: flex;\n    flex-direction: column;\n    justify-content: center;\n    white-space: nowrap;\n    overflow: hidden;\n}\n\n.library-info-col .small {\n    font-weight: 300;\n}\n\n.library-action {\n    margin-left: auto;\n}\n\n.library-info-col .path {\n    font-style: italic !important;\n    font-weight: 300;\n}\n\n/* Theme changer and player button */\n.floating-button {\n    width: 50px;\n    height: 50px;\n    background-color: #aaaaaa40;\n    border-radius: 50%;\n    box-shadow: 0 6px 10px 0 #66666647;\n    transition: all 0.1s ease-in-out;\n    font-size: 25px;\n    color: #9896967a;\n    text-align: center;\n    line-height: 52px;\n    position: fixed;\n    right: 50px;\n}\n\n.floating-button:hover {\n    background-color: hsl(0, 0%, 43%);\n    color: white;\n}\n\n#volume-slider {\n    margin-top: 4px;\n    margin-right: 5px;\n}\n\n.dropdown {\n    display: inline-block;\n}\n\n#volume-popover {\n    position: relative;\n    background: #333;\n    color: white;\n    font-weight: bold;\n    padding: 4px 8px;\n    font-size: 20px;\n    border-radius: 4px;\n    display: none;\n}\n\n#volume-popover[data-show] {\n    display: flex;\n}\n\n#volume-popover a {\n    cursor: pointer;\n}\n\n#volume-popover-arrow,\n#volume-popover-arrow::before {\n    position: absolute;\n    width: 10px;\n    height: 10px;\n    z-index: -1;\n    top: 16px;\n    left: 46px;\n}\n\n#volume-popover-arrow::before {\n    content: '';\n    transform: rotate(45deg);\n    background: #333;\n}\n\n#volume-popover[data-popper-placement^='top']>#volume-popover-arrow {\n    bottom: -4px;\n}\n\n#playerToast {\n    position: fixed;\n    right: 20px;\n    top: 20px;\n    max-width: 800px;\n}\n\n#playerContainer {\n    display: flex;\n    height: 105px;\n}\n\n#playerArtwork {\n    width: 80px;\n    height: 80px;\n    border-radius: 5px;\n}\n\n#playerArtworkIdle {\n    width: 80px;\n    height: 80px;\n    border-radius: 5px;\n    margin: auto;\n    padding: 15px;\n}\n\n#playerInfo {\n    position: relative;\n    padding-top: 6px;\n    margin-left: 10px;\n    height: 80px;\n    font-size: 15px;\n}\n\n#playerTitle {\n    display: block;\n    white-space: nowrap;\n}\n\n#playerArtist {\n    display: block;\n    white-space: nowrap;\n    min-height: 20px;\n}\n\n#playerActionBox {\n    margin-top: 5px;\n    display: flex;\n    float: right;\n}\n\n#playerBarBox {\n    margin-top: 5px;\n    height: 15px;\n    width: 400px;\n    cursor: pointer;\n}\n\n.scrolling {\n    animation: scrolling 8s linear infinite;\n}\n\n@keyframes scrolling {\n    0% {\n        transform: translateX(100%);\n        opacity: 1;\n    }\n\n    95% {\n        transform: translateX(-90%);\n        opacity: 1;\n    }\n\n    100% {\n        transform: translateX(-100%);\n        opacity: 0;\n    }\n}\n\n// Allows us to have H3 with the size of an H5\nh3 {\n    font-size: 1.25rem;\n}\n\n// Makes legend match the size of other labels\nlegend {\n    font-size: 1rem;\n}\n"
  },
  {
    "path": "web/templates/index.template.html",
    "content": "<!DOCTYPE html>\n\n<html lang=\"en\">\n\n<head>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n    <meta charset=\"UTF-8\">\n\n    <title>{{ tr('page_title') }}</title>\n\n    <link rel=\"icon\" href=\"static/image/favicon.ico\" />\n\n    <link id=\"pagestyle\" rel=\"stylesheet\" href=\"static/css/main.css\">\n</head>\n\n<body>\n    <header class=\"container page-header mb-5\" id=\"banner\">\n        <div class=\"row\">\n            <div class=\"col-auto\">\n                <img src=\"static/image/logo.png\" height=\"200px\"\n                                                 alt=\"{{ tr('aria_botamusique_logo') }}\">\n            </div>\n            <div class=\"col my-auto\">\n                <h1>{{ tr('page_title') }}</h1>\n            </div>\n        </div>\n    </header>\n\n    <main id=\"playlist\" class=\"container mb-5\">\n        <div class=\"btn-toolbar mb-2\" role=\"toolbar\" aria-label=\"{{ tr('playlist_controls') }}\">\n            <button type=\"button\" id=\"play-pause-btn\" class=\"btn btn-info mb-2 btn-space\" aria-label=\"{{ tr('play') }}\">\n                <i class=\"fas fa-play\"></i>\n            </button>\n            <button type=\"button\" id=\"fast-forward-btn\" class=\"btn btn-info mb-2\" aria-label=\"{{ tr('skip_track') }}\">\n                <i class=\"fas fa-fast-forward\"></i>\n            </button>\n            <div class=\"ml-auto\">\n                <div class=\"dropdown mr-2\">\n                    <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" id=\"play-mode\"\n                        data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\"\n                        aria-label=\"{{ tr('change_playback_mode') }}\">\n                        <i class=\"fas fa-tasks mr-2\" aria-hidden=\"true\" id=\"modeIndicator\"></i>\n                    </button>\n                    <div class=\"dropdown-menu\" aria-labelledby=\"play-mode\">\n                        <a class=\"dropdown-item\" href=\"#\" id=\"one-shot-mode-btn\">\n                            <i class=\"fas fa-tasks mr-2\" aria-hidden=\"true\"></i> {{ tr('oneshot') }}\n                        </a>\n                        <a class=\"dropdown-item\" href=\"#\" id=\"random-mode-btn\">\n                            <i class=\"fas fa-random mr-2\" aria-hidden=\"true\"></i> {{ tr('random') }}\n                        </a>\n                        <a class=\"dropdown-item\" href=\"#\" id=\"repeat-mode-btn\">\n                            <i class=\"fas fa-redo mr-2\" aria-hidden=\"true\"></i> {{ tr('repeat') }}\n                        </a>\n                        <a class=\"dropdown-item\" href=\"#\" id=\"autoplay-mode-btn\">\n                            <i class=\"fas fa-robot mr-2\" aria-hidden=\"true\"></i> {{ tr('autoplay') }}\n                        </a>\n                    </div>\n                </div>\n                <button type=\"button\" id=\"volume-popover-btn\" class=\"btn btn-warning ml-1\"\n                                                              aria-label=\"{{ tr('open_volume_controls') }}\">\n                    <i class=\"fa fa-volume-up\" aria-hidden=\"true\"></i>\n                </button>\n\n                <div id=\"volume-popover\">\n                    <a id=\"volume-down-btn\">\n                        <i class=\"fa fa-volume-down\" aria-hidden=\"true\"></i>\n                    </a>\n\n                    <input type=\"range\" class=\"custom-range ml-1\" id=\"volume-slider\" min=\"0\" max=\"1\" step=\"0.01\"\n                                                                                                     value=\"0.5\" aria-label=\"{{ tr('volume_slider') }}\" />\n\n                    <a id=\"volume-up-btn\">\n                        <i class=\"fa fa-volume-up\" aria-hidden=\"true\"></i>\n                    </a>\n                    <div id=\"volume-popover-arrow\"></div>\n                </div>\n            </div>\n        </div>\n\n        <div class=\"table-responsive\">\n            <table class=\"table table-striped table-hover\">\n                <thead>\n                    <tr>\n                        <th scope=\"col\" class=\"d-none d-md-table-cell\">{{ tr('index') }}</th>\n                        <th scope=\"col\" class=\"w-50\">{{ tr('title') }}</th>\n                        <th scope=\"col\" class=\"d-none d-md-table-cell\">{{ tr('url_path') }}</th>\n                        <th scope=\"col\">{{ tr('action') }}</th>\n                    </tr>\n                </thead>\n                <tbody id=\"playlist-table\" class=\"playlist-table\">\n                    <tr id=\"playlist-loading\">\n                        <td colspan=\"4\" class=\"text-center\">\n                            <img style=\"margin: auto; width: 35px;\" src=\"static/image/loading.svg\"\n                                                                    alt=\"{{ tr('aria_spinner') }}\" />\n                        </td>\n                    </tr>\n                    <tr id=\"playlist-empty\" class=\"d-none\">\n                        <td colspan=\"4\" class=\"text-center\">\n                            <img style=\"margin: auto; width: 35px;\" src=\"static/image/empty_box.svg\"\n                                                                    alt=\"{{ tr('aria_empty_box') }}\" />\n                        </td>\n                    </tr>\n                    <tr class=\"playlist-expand table-dark d-none\">\n                        <td colspan=\"4\" class=\"text-center\">\n                            <a class=\"text-muted\" href=\"javascript:\">{{ tr('expand_playlist') }}</a>\n                        </td>\n                    </tr>\n                    <tr class=\"playlist-item-template d-none\" aria-hidden=\"true\">\n                        <th scope=\"row\" class=\"playlist-item-index d-none d-md-table-cell\">1</th>\n                        <td>\n                            <input hidden type=\"hidden\" class=\"playlist-item-id\" value=\"\" />\n                            <div class=\"float-left\">\n                                <img width=\"80\" class=\"playlist-item-thumbnail\" src=\"static/image/unknown-album.png\"\n                                                                                alt=\"{{ tr('aria_default_cover') }}\" />\n                            </div>\n                            <div class=\"playlist-artwork\">\n                                <b class=\"playlist-item-title\"></b>\n                                <span class=\"playlist-item-type badge badge-secondary\"></span>\n                                <br />\n                                <span class=\"playlist-item-artist\"></span>\n                                <br />\n\n                                <div class=\"playlist-item-tags\">\n                                    <a class=\"playlist-item-edit tag-space tag-click\">\n                                        <i class=\"fas fa-edit\" style=\"color: #AAAAAA\"></i>\n                                    </a>\n                                </div>\n                            </div>\n                        </td>\n                        <td class=\"d-none d-md-table-cell\">\n                            <small class=\"playlist-item-path\"></small>\n                        </td>\n                        <td>\n                            <div class=\"btn-group\">\n                                <button type=\"button\" class=\"playlist-item-play btn btn-info btn-sm\"\n                                                      aria-label=\"{{ tr('aria_skip_current_song') }}\">\n                                    <i class=\"fas fa-play\" aria-hidden=\"true\"></i>\n                                </button>\n                                <button type=\"button\" class=\"playlist-item-trash btn btn-danger btn-sm ml-1\"\n                                                      aria-label=\"{{ tr('aria_remove_this_song') }}\">\n                                    <i class=\"fas fa-trash-alt\" aria-hidden=\"true\"></i>\n                                </button>\n                            </div>\n                        </td>\n                    </tr>\n                </tbody>\n            </table>\n        </div>\n\n        <div class=\"btn-group\">\n            <button type=\"button\" id=\"clear-playlist-btn\" class=\"btn btn-danger mr-1\">\n                <i class=\"fas fa-trash-alt\" aria-hidden=\"true\"></i> {{ tr('clear_playlist') }}</button>\n        </div>\n    </main>\n\n    <div class=\"container mb-3\">\n        <h2 id=\"forms\">{{ tr('music_library') }}</h2>\n\n        <div class=\"card mb-3\">\n            <div class=\"card-header\">\n                <h3 class=\"card-title\">{{ tr('filters') }}</h3>\n                <hr>\n                <div class=\"row\">\n                    <div class=\"col\">\n                        <fieldset id=\"filter-type\" class=\"mb-2\">\n                            <legend>{{ tr('type') }}</legend>\n                            <div class=\"btn-group btn-group-sm btn-group-toggle\">\n                                <label id=\"filter-type-file\" class=\"btn btn-secondary\">\n                                    <input type=\"checkbox\" name=\"options\">{{ tr('file') }}\n                                </label>\n                                <label id=\"filter-type-url\" class=\"btn btn-secondary\">\n                                    <input type=\"checkbox\" name=\"options\">{{ tr('url') }}\n                                </label>\n                                <label id=\"filter-type-radio\" class=\"btn btn-secondary\">\n                                    <input type=\"checkbox\" name=\"options\">{{ tr('radio') }}\n                                </label>\n                            </div>\n                        </fieldset>\n\n                        <label for=\"filter-dir\">{{ tr('directory') }}</label>\n                        <div id=\"filter-path\" class=\"input-group mb-2\">\n                            <select class=\"form-control form-control-sm\" id=\"filter-dir\" disabled>\n                            </select>\n                        </div>\n\n                        <label for=\"filter-keywords\">{{ tr('keywords') }}</label>\n                        <div id=\"filter-path\" class=\"input-group mb-2\">\n                            <input class=\"form-control form-control-sm\" id=\"filter-keywords\" name=\"keywords\"\n                                                                                             placeholder=\"{{ tr('keywords_placeholder') }}\" style=\"margin-top:5px;\" />\n                        </div>\n                    </div>\n\n                    <div class=\"col\">\n                        <fieldset id=\"filter-tags\">\n                            <legend>{{ tr('tags') }}</legend>\n                            <span class=\"filter-tag tag-unclicked tag-click badge\"></span>\n                        </fieldset>\n                    </div>\n                </div>\n            </div>\n            <div class=\"card-body\">\n                <div id=\"library-group\" class=\"list-group library-group\" style=\"overflow: auto;\">\n                    <input type=\"hidden\" id=\"deleteAllowed\" value=\"true\" />\n                    <div id=\"library-item-loading\" class=\"list-group-item library-item\">\n                        <img style=\"margin: auto; width: 35px;\" src=\"static/image/loading.svg\"\n                                                                alt=\"{{ tr('aria_spinner') }}\" />\n                    </div>\n                    <div id=\"library-item-empty\" style=\"display: none\" class=\"list-group-item library-item\">\n                        <img style=\"margin: auto; width: 35px;\" src=\"static/image/empty_box.svg\"\n                                                                alt=\"{{ tr('aria_empty_box') }}\" />\n                    </div>\n                    <div id=\"library-item\" style=\"display: none;\" class=\"list-group-item library-item\">\n                        <input hidden type=\"hidden\" class=\"library-item-id\" value=\"\" />\n\n                        <div class=\"library-thumb-col\">\n                            <div class=\"library-thumb-img\">\n                                <img class=\"library-item-thumb library-thumb-img\" src=\"static/image/unknown-album.png\"\n                                                                                  alt=\"{{ tr('aria_default_cover') }}\" />\n                            </div>\n                            <div class=\"btn-group-vertical library-thumb-grp\">\n                                <div class=\"library-item-play btn btn-secondary library-thumb-btn-up\" title=\"{{ tr('play') }}\">\n                                    <i class=\"fas fa-play\" aria-hidden=\"true\"></i>\n                                </div>\n                            </div>\n                        </div>\n\n                        <div class=\"library-info-col library-info-title col-5\" style=\"padding: 12px 0;\">\n                            <div>\n                                <span class=\"library-item-type lead text-muted btn-space\">[File]</span>\n                                <span class=\"library-item-title lead btn-space\">This is my title</span>\n                                <span class=\"library-item-artist text-muted\"> - Artist</span>\n                            </div>\n                        </div>\n\n                        <div class=\"library-info-col col-4 d-none d-md-flex\" style=\"padding: 3px;\">\n                            <span class=\"library-item-path text-muted path\">Path/to/the/file</span>\n                            <div class=\"library-item-tags\">\n                                <a class=\"tag-space tag-click library-item-edit\"><i class=\"fas fa-edit\"\n                                        style=\"color: #AAAAAA\"></i></a>\n                                <span class=\"library-item-notag badge badge-light text-muted font-italic\">{{ tr('no_tag') }}</span>\n                                <span class=\"library-item-tag tag-space badge\">Tag</span>\n                            </div>\n                        </div>\n\n                        <div class=\"btn-group library-action\">\n                            <button class=\"library-item-add-next btn btn-info btn-sm btn-space\" type=\"button\"\n                                                                                                title=\"{{ tr('next_to_play') }}\" aria-label=\"{{ tr('add_to_playlist_next') }}\">\n                                <svg class=\"library-btn-svg\" style=\"width: 1rem; fill: currentColor;\"\n                                    viewBox=\"5 5 17 17\">\n                                    <path d=\"m5.700245,3.92964l0,14.150376l11.451127,-7.075188l-11.451127,-7.075188z\">\n                                    </path>\n                                    <path\n                                        d=\"m20.942859,18.221072l-3.323292,0l0,3.323292l-1.107764,0l0,-3.323292l-3.323292,0l0,-1.107764l3.323292,0l0,-3.323292l1.107764,0l0,3.323292l3.323292,0l0,1.107764z\">\n                                    </path>\n                                </svg>\n                            </button>\n                            <button class=\"library-item-add-bottom library-btn btn btn-info btn-sm btn-space\"\n                                    type=\"button\" title=\"{{ tr('add_to_bottom') }}\" aria-label=\"{{ tr('add_to_bottom_of_current_playlist') }}\">\n                                <svg class=\"library-btn-svg\" style=\"width: 1rem; fill: currentColor;\"\n                                    viewBox=\"2 2 20 20\">\n                                    <path\n                                        d=\"M2,16H10V14H2M18,14V10H16V14H12V16H16V20H18V16H22V14M14,6H2V8H14M14,10H2V12H14V10Z\">\n                                    </path>\n                                </svg>\n                            </button>\n                            <button class=\"library-item-download btn btn-primary btn-sm btn-space\" type=\"button\"\n                                                                                                   aria-label=\"{{ tr('download_song_from_library') }}\">\n                                <i class=\"fas fa-download\" aria-hidden=\"true\"></i>\n                            </button>\n                            <button class=\"library-item-trash btn btn-danger btn-sm btn-space library-delete\" type=\"button\"\n                                                                                               aria-label=\"{{ tr('remove_song_from_library') }}\">\n                                <i class=\"fas fa-trash-alt\" aria-hidden=\"true\"></i>\n                            </button>\n                        </div>\n                    </div>\n\n                </div>\n\n                <div class=\"list-group\">\n                    <div id=\"library-pagination\" style=\"margin-left: auto; margin-top: 10px;\">\n                        <ul id=\"library-page-ul\" class=\"pagination pagination\">\n                            <li class=\"library-page-li page-item ctive\">\n                                <a class=\"library-page-no page-link\">1</a>\n                            </li>\n                        </ul>\n                    </div>\n                </div>\n\n                <div class=\"btn-group mb-2\" role=\"group\">\n                    <button id=\"add-to-playlist-btn\" type=\"button\" class=\"btn btn-secondary mr-1\">\n                        <i class=\"fas fa-plus\" aria-hidden=\"true\"></i>{{ tr('add_all') }}\n                    </button>\n                    <button id=\"library-rescan-btn\" type=\"button\" class=\"btn btn-secondary mr-1\">\n                        <i class=\"fas fa-sync-alt\" aria-hidden=\"true\"></i>{{ tr('rescan_files') }}\n                    </button>\n                    <button id=\"library-download-btn\" type=\"button\" class=\"btn btn-secondary mr-1\">\n                        <i class=\"fas fa-download\" aria-hidden=\"true\"></i>{{ tr('download_all') }}\n                    </button>\n                    <button type=\"button\" class=\"btn btn-danger mr-1 library-delete\" data-toggle=\"modal\"\n                        data-target=\"#deleteWarningModal\">\n                        <i class=\"fas fa-trash-alt\" aria-hidden=\"true\"></i>{{ tr('delete_all') }}\n                    </button>\n                </div>\n\n                <div class=\"modal fade\" id=\"deleteWarningModal\" tabindex=\"-1\" role=\"dialog\"\n                                                                              aria-label=\"{{ tr('aria_warning_of_deletion') }}\" aria-hidden=\"true\">\n                    <div class=\"modal-dialog\" role=\"document\">\n                        <div class=\"modal-content\">\n                            <div class=\"modal-header\">\n                                <h3 class=\"modal-title\" id=\"deleteWarningModalLabel\">{{ tr('are_you_really_sure') }}</h3>\n                                <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"{{ tr('close') }}\">\n                                    <span aria-hidden=\"true\">&times;</span>\n                                </button>\n                            </div>\n                            <div class=\"modal-body\">\n                                {{ tr('delete_file_warning') }}</div>\n                            <div class=\"modal-footer\">\n                                <button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">{{ tr('close') }}</button>\n                                <button id=\"library-delete-btn\" type=\"button\" class=\"btn btn-danger\"\n                                                                              data-dismiss=\"modal\">{{ tr('delete_all_files') }}</button>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <div id=\"upload\" class=\"container mb-3\">\n        <input type=\"hidden\" id=\"uploadDisabled\" value=\"false\" />\n        <div class=\"card\">\n            <div class=\"card-header\">\n                <h3 class=\"card-title\">{{ tr('upload_file') }}</h3>\n            </div>\n            <div class=\"card-body\">\n                <form action=\"./upload\" method=\"post\" enctype=\"multipart/form-data\">\n                    <div class=\"row\">\n                        <div id=\"uploadBox\" class=\"col-lg-7\">\n                            <div class=\"input-group mb-3\">\n                                <div id=\"uploadField\" style=\"display: flex; width: 100%\">\n                                    <div class=\"custom-file\">\n                                        <input type=\"file\" name=\"file[]\" class=\"custom-file-input\" id=\"uploadSelectFile\" aria-describedby=\"uploadSubmit\" value=\"{{ tr('browse_music_file') }}\" multiple />\n                                        <label class=\"custom-file-label\" for=\"uploadSelectFile\">{{ tr('choose_file') }}</label>\n                                    </div>\n                                </div>\n                            </div>\n                        </div>\n                        <div class=\"col-lg-5\">\n                            <div class=\"row\">\n                                <div class=\"col\">\n                                    <div class=\"input-group mb-3\">\n                                        <div class=\"input-group-prepend\">\n                                            <label for=\"uploadTargetDir\" class=\"input-group-text\">{{ tr('upload_to') }}</label>\n                                        </div>\n                                        <input class=\"form-control\" list=\"upload-target-dirs\" id=\"uploadTargetDir\" name=\"upload-target-dirs\" placeholder=\"uploads\" />\n                                        <datalist id=\"upload-target-dirs\">\n                                        </datalist>\n                                    </div>\n                                </div>\n                                <div class=\"col-auto\">\n                                    <button class=\"btn btn-primary\" type=\"button\" id=\"uploadSubmit\"><i class=\"fas fa-upload mr-1\"></i>{{ tr('upload_submit') }}</button>\n                                </div>\n                            </div>\n                        </div>\n                    </div>\n                </form>\n            </div>\n        </div>\n    </div>\n\n    <div class=\"container mb-5\">\n        <div class=\"card-deck\">\n            <div id=\"add-music-url\" class=\"card\">\n                <div class=\"card-header\">\n                    <h3 class=\"card-title\">{{ tr('add_url') }}</h3>\n                </div>\n                <div class=\"card-body\">\n                    <label for=\"music-url-input\">{{ tr('add_youtube_or_soundcloud_url') }}</label>\n                    <div class=\"input-group mb-2\">\n                        <input class=\"form-control\" type=\"text\" id=\"music-url-input\" placeholder=\"{{ tr('url_placeholder') }}\">\n                    </div>\n                    <button type=\"submit\" class=\"btn btn-primary\">\n                        {{ tr('add_url') }}\n                    </button>\n                </div>\n            </div>\n            <div id=\"add-radio-url\" class=\"card\">\n                <div class=\"card-header\">\n                    <h3 class=\"card-title\">{{ tr('add_radio') }}</h3>\n                </div>\n                <div class=\"card-body\">\n                    <label for=\"radio-url-input\">{{ tr('add_radio_url') }}</label>\n                    <div class=\"input-group mb-2\">\n                        <input id=\"radio-url-input\" class=\"form-control\" type=\"text\" placeholder=\"{{ tr('radio_url_placeholder') }}\">\n                    </div>\n                    <button type=\"submit\" class=\"btn btn-primary\">\n                        {{ tr('add_radio') }}\n                    </button>\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <div id=\"player-toast\" class=\"floating-button\" style=\"bottom: 120px;\">\n        <i class=\"fas fa-play\" aria-hidden=\"true\"></i>\n    </div>\n\n    <div id=\"theme-switch-btn\" class=\"floating-button\" style=\"bottom: 50px;\">\n        <i class=\"fas fa-lightbulb\" aria-hidden=\"true\"></i>\n    </div>\n\n    <div id=\"playerToast\" class=\"toast\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\" data-autohide=\"false\">\n        <div class=\"toast-header\">\n            <i class=\"fas fa-play-circle mr-2 text-primary\"></i>\n            <strong class=\"mr-auto\">{{ tr('mini_player_title') }}</strong>\n            <button type=\"button\" class=\"ml-2 mb-1 close\" data-dismiss=\"toast\" aria-label=\"Close\">\n                <span aria-hidden=\"true\">&times;</span>\n            </button>\n        </div>\n        <div class=\"toast-body\" id=\"playerContainer\">\n            <img id=\"playerArtworkIdle\" src=\"static/image/empty_box.svg\" alt=\"{{ tr('aria_empty_box') }}\" />\n            <img id=\"playerArtwork\" src=\"static/image/unknown-album.png\" style=\"display: none;\"\n                                                                         alt=\"{{ tr('aria_default_cover') }}\" />\n            <div id=\"playerInfo\">\n                <div id=\"playerActionBox\">\n                    <button id=\"playerPlayBtn\" class=\"btn btn-primary btn-sm btn-space\" style=\"display: none\"\n                                                                                        aria-label=\"{{ tr('play') }}\">\n                        <i class=\"fas fa-play\"></i>\n                    </button>\n                    <button id=\"playerPauseBtn\" class=\"btn btn-primary btn-sm btn-space\" style=\"display: none\"\n                                                                                         aria-label=\"{{ tr('pause') }}\">\n                        <i class=\"fas fa-pause\"></i>\n                    </button>\n                    <button id=\"playerSkipBtn\" class=\"btn btn-primary btn-sm\" aria-label=\"{{ tr('aria_skip_to_next_track') }}\">\n                        <i class=\"fas fa-fast-forward\"></i>\n                    </button>\n                </div>\n\n                <div style=\"overflow: hidden; max-width: 320px;\">\n                    <strong id=\"playerTitle\">Song Title</strong>\n                </div>\n                <span id=\"playerArtist\">Artist</span>\n                <div id=\"playerBarBox\" class=\"progress\">\n                    <div id=\"playerBar\" class=\"progress-bar pr-2\" role=\"progressbar\" aria-valuenow=\"50\"\n                        aria-valuemin=\"0\" aria-valuemax=\"100\"\n                        style=\"width: 100%; text-align: right; transform: translateX(-100%);\"></div>\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <div id=\"footer\" style=\"height:50px; width: 100%; margin-top: 100px;\"></div>\n\n    <form id=\"download-form\" action=\"download\" method=\"GET\" target=\"_blank\">\n        <input hidden type=\"hidden\" name=\"id\" value=\"\">\n        <input hidden type=\"hidden\" name=\"type\" value=\"\">\n        <input hidden type=\"hidden\" name=\"dir\" value=\"\">\n        <input hidden type=\"hidden\" name=\"tags\" value=\"\">\n        <input hidden type=\"hidden\" name=\"keywords\" value=\"\">\n    </form>\n\n    <!-- Add tags modal -->\n    <div class=\"modal fade\" id=\"addTagModal\" tabindex=\"-1\" role=\"dialog\">\n        <div class=\"modal-dialog\" role=\"document\">\n            <div class=\"modal-content\">\n                <div class=\"modal-header\">\n                    <h3 class=\"modal-title\">{{ tr('edit_tags_for') }} <span id=\"addTagModalTitle\">?</span></h3>\n                    <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">\n                        <span aria-hidden=\"true\">&times;</span>\n                    </button>\n                </div>\n                <div id=\"addTagModalBody\" class=\"modal-body\">\n                    <input hidden type=\"hidden\" id=\"addTagModalItemId\" name=\"id\" value=\"\">\n                    <div class=\"modal-tag\" style=\"display: none; width: 100%;\">\n                        <span class=\"modal-tag-text tag-space badge badge-pill badge-dark\">Tag</span>\n                        <a class=\"modal-tag-remove tag-click small\"><i\n                                class=\"fas fa-times-circle btn-outline-danger\"></i></a>\n                    </div>\n                    <div id=\"addTagModalTags\" style=\"margin-left: 5px; margin-bottom: 10px;\">\n                    </div>\n                    <div class=\"input-group\">\n                        <input class=\"form-control form-control-sm btn-space\" type=\"text\" id=\"addTagModalInput\"\n                                                                                          placeholder=\"tag1,tag2,...\" aria-label=\"{{ tr('tags_to_add') }}\">\n                        <button id=\"addTagModalAddBtn\" type=\"button\" class=\"btn btn-primary btn-sm\">\n                            <i class=\"fas fa-plus\" aria-hidden=\"true\"></i>\n                            {{ tr('add') }}\n                        </button>\n                    </div>\n                </div>\n                <div class=\"modal-footer\">\n                    <button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">{{ tr('close') }}</button>\n                    <button id=\"addTagModalSubmit\" type=\"button\" class=\"btn btn-success\"\n                                                                 data-dismiss=\"modal\">{{ tr('edit_submit') }}</button>\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <!-- Upload files modal -->\n    <div class=\"modal fade\" id=\"uploadModal\" tabindex=\"-1\" role=\"dialog\">\n        <div class=\"modal-dialog\" role=\"document\">\n            <div class=\"modal-content\">\n                <div class=\"modal-header\">\n                    <h3 class=\"modal-title\" id=\"uploadTitle\"><i class=\"fas fa-upload mr-1\"></i>{{ tr('uploading_files') }}</h3>\n                </div>\n                <div id=\"uploadModalBody\" class=\"modal-body\">\n                    <div id=\"uploadSuccessAlert\" class=\"alert alert-success\" role=\"alert\" style=\"display: none\">\n                        <i class=\"fas fa-check mr-1\"></i>\n                        {{ tr('uploaded_finished') }}\n                    </div>\n                    <div id=\"uploadModalList\" style=\"margin-left: 5px; margin-bottom: 10px;\">\n                        <div class=\"uploadItem\" style=\"display: none; width: 100%; padding-bottom: 8px;\">\n                            <i class=\"far fa-file-alt mr-1\"></i>\n                            <span class=\"uploadItemTitle mr-3\"></span>\n                            <span class=\"uploadItemError text-danger\"></span>\n                            <div class=\"progress\" style=\"margin-top: 5px; height: 10px;\">\n                                <div class=\"uploadProgress progress-bar pr-2\" role=\"progressbar\" aria-valuenow=\"0\"\n                                    aria-valuemin=\"0\" aria-valuemax=\"100\"\n                                    style=\"width: 100%; text-align: right; transform: translateX(-100%);\"></div>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n                <div class=\"modal-footer\">\n                    <button type=\"button\" id=\"uploadClose\" class=\"btn btn-success\" data-dismiss=\"modal\">\n                        <i class=\"fas fa-times mr-1\"></i> {{ tr('close') }}</button>\n                    <button type=\"button\" id=\"uploadCancel\" class=\"btn btn-danger\" data-toggle=\"tooltip\"\n                        data-html=\"true\"\n                        title=\"{{ tr('cancel_upload_warning') }}\">\n                        <i class=\"fas fa-trash-alt mr-1\" aria-hidden=\"true\"></i> {{ tr('cancel') }}</button>\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <input type=\"hidden\" id=\"maxUploadFileSize\" value=\"\" />\n\n    <script src=\"static/js/main.js\"></script>\n</body>\n\n</html>\n"
  },
  {
    "path": "web/templates/need_token.template.html",
    "content": "<!DOCTYPE html>\n\n<head>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n    <meta charset=\"UTF-8\">\n\n    <title>{{ tr('page_title') }}</title>\n\n    <link rel=\"icon\" href=\"static/image/favicon.ico\" />\n\n    <link rel=\"stylesheet\" href=\"static/css/main.css\">\n</head>\n\n<body>\n    <div class=\"container\" style=\"max-width: 800px\">\n        <div class=\"col-8\" style=\"margin: auto; padding-top: 50px;\">\n            <div class=\"card\">\n                <div class=\"card-header\">\n                    {{ tr('token_required') }}\n                </div>\n                <div class=\"card-body\">\n                    <h3>{{ tr('token_required') }}</h3>\n                    {{ tr('token_required_message') }}\n                    <form action=\".\" method=\"get\">\n                        <div class=\"form-group mt-3\">\n                            <label for=\"token_input\">{{ tr('token') }}</label>\n                            <div class=\"input-group\">\n                                <input type=\"password\" class=\"form-control btn-space\" id=\"token_input\" name=\"token\" placeholder=\"xxxxxxx\">\n                                <button type=\"submit\" class=\"btn btn-primary\">{{ tr('submit') }}</button>\n                            </div>\n                        </div>\n                    </form>\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <script src=\"static/js/main.js\"></script>\n</body>\n\n</html>\n"
  },
  {
    "path": "web/vscode.eslintrc.json",
    "content": "{\n  \"parserOptions\": {\n    \"babelOptions\": {\n      \"configFile\": \"./web/babel.config.json\"\n    }\n  }\n}\n"
  },
  {
    "path": "web/webpack.config.cjs",
    "content": "const path = require('path');\nconst MiniCssExtractPlugin = require('mini-css-extract-plugin');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\n\nmodule.exports = {\n  mode: 'production',\n  devtool: 'source-map',\n  entry: {\n    main: [\n      './js/app.mjs',\n      './sass/app.scss',\n    ],\n    dark: [\n      './sass/app-dark.scss',\n    ],\n  },\n  output: {\n    filename: 'static/js/[name].js',\n    path: path.resolve(__dirname, '../'),\n  },\n  plugins: [\n    new MiniCssExtractPlugin({\n      filename: 'static/css/[name].css',\n    }),\n    new HtmlWebpackPlugin({\n      filename: 'templates/index.template.html',\n      template: './templates/index.template.html',\n      inject: false,\n    }),\n    new HtmlWebpackPlugin({\n      filename: 'templates/need_token.template.html',\n      template: './templates/need_token.template.html',\n      inject: false,\n    }),\n  ],\n  module: {\n    rules: [{\n      test: /\\.s[ac]ss$/i,\n      use: [\n        MiniCssExtractPlugin.loader,\n        'css-loader', // translates CSS into CommonJS modules\n        {\n          loader: 'postcss-loader',\n          options: {\n            postcssOptions: {\n              plugins: [\n                [\n                  'autoprefixer',\n                  {\n                    // Options\n                  },\n                ],\n              ],\n            },\n          },\n        },\n        'sass-loader', // compiles Sass to CSS\n      ],\n    },\n    {\n      test: /\\.m?js$/,\n      exclude: /(node_modules|bower_components)/,\n      resolve: {\n        fullySpecified: false,\n      },\n      use: {\n        loader: 'babel-loader',\n        options: {\n          presets: [\n            [\n              '@babel/preset-env',\n              {\n                'corejs': '3.6',\n                'useBuiltIns': 'usage',\n              },\n            ],\n          ],\n        },\n      },\n    },\n    ],\n  },\n};\n"
  }
]