October 18, 2025

Бот-Экспедитор Telegram

  • Автоматическая пересылка сообщений из каналов и групп (личных или общедоступных)
  • Объедините каналы в тематические ленты
  • Клонируйте все сообщения из любого канала в свой собственный канал

Предварительная установка

Вам понадобятся два разных ключа API. Один для API телеграм-бота, а другой для аккаунта в Telegram, используемого в качестве агента.

A. API бота

elegram предлагает удобный способ создания API-ключей для телеграм-ботов. Вам нужно отправить сообщение @botfather — телеграм-боту, который поможет вам создать новых ботов! Бот проведет вас через процесс создания новых ботов и управления ими, а также через процесс создания API-ключей.

B. Telegram API

Чтобы получить идентификатор API и хэш-идентификатор для аккаунта в Telegram, перейдите по ссылке https://my.telegram.org/auth?to=apps

Установка

Клонировать репозиторий

git clone https://github.com/adityathebe/telegramForwarder.git

1. Docker (рекомендуется)

Для файла docker-compose требуется несколько переменных среды. Создайте файл .env в корневом каталоге. Docker Compose автоматически извлечёт необходимые переменные среды из этого файла. Вот пример моего файла .env

TG_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
TG_API_ID=XXXX
TG_HASH_ID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
TG_BOT_USERNAME=@XXXXXXXXXXXXXXXXXbot

With that set up you're ready to go !

докер-создание
При первом запуске этой команды могут возникнуть проблемы. Если это произойдёт, остановите процесс и запустите команду повторно

2. Руководство пользователя

1. Установка пакетов Python

cd агент
pip install -r requirements.txt

2. Установите пакеты Nodejs

cd бот
npm установить

3. Установите и настройте Postgress

Установите сервер Postgres и создайте новую базу данных под названием telegram.

4. Запускайте все подряд

Последующая установка

После того как вы настроите базу данных, агент Python и сервер Node, вам нужно будет войти в Telegram-аккаунт, который вы хотите использовать в качестве агента пересылки.

Чтобы войти в систему, перейдите по адресу http://localhost:3000/login

Что нужно иметь в виду

  1. Если вы запускаете код с помощью Docker и Docker Compose, вам нужно будет пересобрать образы Docker после установки новых обновлений.
docker-compose up --build
  1. Docker сохранит базу данных postgres даже после остановки контейнеров Docker. Если по какой-то причине вы хотите начать с чистого листа и очистить базу данных, вам нужно очистить том Docker.
docker-compose down -v

Доступ к базе данных из Docker-контейнера

Если вы уже установили psql или любой другой графический интерфейс для Postgres, то можете просто подключиться к базе данных, так как порт 5432 открыт и сопоставлен с портом 5432.

Однако, если у вас нет ни одного из этих инструментов и вы не хотите заморачиваться с их установкой, вы можете просто запустить интерактивную оболочку в Docker-контейнере и получить доступ к базе данных оттуда. Если вы выполните приведённую ниже команду, вы получите оболочку в Docker-контейнере postgres

Docker-контейнер exec -it telegramforwarder_postgres-db_1 psql -U postgres
Примечание 1. Образ PostgreSQL настраивает локальную аутентификацию по доверию, поэтому при подключении с локального хоста (внутри того же контейнера) пароль может не требоваться. Однако при подключении с другого хоста/контейнера потребуется пароль.
ИЗ*
выбрать --  \c telegram пользователей;

Вопросы и ответы:

— В: Как начать пользоваться ботом?
О: Отправьте боту команду /start и следуйте инструкциям.

Вопрос: нужны ли боту права администратора в канале/группе, из которых он осуществляет пересылку?
Ответ: нет.

Вопрос: нужны ли боту права администратора в канале/группе, на которые он перенаправляет сообщения?
Ответ: да.

Вопрос: Можно ли отфильтровать рекламу или медиаконтент (видео, стикеры и т. д.)?
Ответ: Да.

Вопрос: Можно ли настроить автоматическую переадресацию с другого бота?
Ответ: Да.

Сам код:

✉Агент

✉ конфигурация

↪️__init__.py

import os
API_PORT = os.environ['API_PORT']
# Telegram configs
api_id = os.environ['TG_API_ID']
api_hash = os.environ['TG_HASH_ID']
# Database configs
DB_HOST = os.environ.get('DB_HOST') or 'localhost'
DB_SESSION_DBNAME = "telethonsession"
# Telethon Session settings
TELETHON_SESSION_ID = 'synapticSupport'
session_name_forwarder = 'synapticSupportForwarder'

✉дб

↪️database.py

port psycopg2
from config import DB_HOST
class Database:
    def __init__(self):
        self.db = psycopg2.connect(
            host=DB_HOST,
            database="telegram",
            user="postgres",
            password="mysecretpassword")
    def get_user(self, user_id):
        cursor = self.db.cursor()
        sql = f"SELECT * FROM users WHERE chat_id = '{user_id}'"
        cursor.execute(sql)
        result = cursor.fetchone()
        cursor.close()
        return result
    def get_active_redirections_of_source(self, source):
        cursor = self.db.cursor()
        sql = f"SELECT * FROM redirections WHERE source = '{source}' AND active = TRUE"
        cursor.execute(sql)
        result = cursor.fetchall()
        cursor.close()
        return result
    def get_redirection(self, redirection_id):
        """"
        Get redirection of given redirection id
        """
        cursor = self.db.cursor()
        sql = f"SELECT * FROM redirections WHERE id = '{redirection_id}' AND active = 1"
        cursor.execute(sql)
        result = cursor.fetchall()
        cursor.close()
        return result
    def get_all_redirections(self):
        cursor = self.db.cursor()
        sql = "SELECT * FROM redirections;"
        cursor.execute(sql)
        result = cursor.fetchall()
        cursor.close()
        return result
    def get_filter(self, filter_id):
        cursor = self.db.cursor()
        sql = f"SELECT * FROM filters WHERE id = {filter_id}"
        cursor.execute(sql)
        result = cursor.fetchone()
        cursor.close()
        return result
    def get_transformations_of_redirection(self, redirection_id):
        cursor = self.db.cursor()
        sql = f"SELECT * FROM transformations WHERE redirection_id = {redirection_id} ORDER BY rank"
        cursor.execute(sql)
        result = cursor.fetchall()
        cursor.close()
        return result

✉Утилиты

↪️filter.py

import logging
from db.database import Database
logger = logging.getLogger('Filter')
database = Database()
class MessageFilter:
    @staticmethod
    def get_filter(filter_id):
        filter = database.get_filter(filter_id)
        if filter is None:
            return False
        filter_dict = {
            'id': filter[0],
            'audio': filter[1],
            'video': filter[2],
            'photo': filter[3],
            'sticker': filter[4],
            'document': filter[5],
            'hashtag': filter[6],
            'link': filter[7],
            'contain': filter[8],
            'notcontain': filter[9]
        }
        return filter_dict
    @staticmethod
    def get_active_filters(filter_dict):
        """
        Takes in a filter dictionary
        Returns a list of active filter names
        Example: ('photo', 'audio')
        """
        if not isinstance(filter_dict, dict):
            raise ValueError('Provide a dictionary')
        active_filter_list = []
        for key, value in filter_dict.items():
            if value != 0 and value is not None:
                if key != 'id':
                    active_filter_list.append(key)
        return active_filter_list
    @staticmethod
    def get_message_type(event):
        # Photos Messages
        if hasattr(event.media, 'photo'):
            return 'photo'
        # Look for links and hashtags in event.entities
        if event.entities is not None:
            for entity in event.entities:
                entity_name = type(entity).__name__
                if entity_name == 'MessageEntityHashtag':
                    return 'hashtag'
                if entity_name == 'MessageEntityUrl':
                    return 'link'
        # Text Messages
        if event.media == None:
            return 'text'
        # Documents (audio, video, sticker, files)
        mime_type = event.media.document.mime_type
        if 'audio' in mime_type:
            return 'audio'
        if 'video' in mime_type:
            return 'video'
        if 'image/webp' in mime_type:
            return 'sticker'
        # Anything else is a file
        return 'document'
    @staticmethod
    def filter_msg(filter_id, message_event):
        """
        Function that decides if a message should be forwarded or not
        Returns Boolean
        """
        # Get Filter dictionary from database
        filter_dict = MessageFilter.get_filter(filter_id)
        if filter_dict == False:
            logger.info('No filters for id : {}'.format(filter_id))
            return False
        # Get Active Filter list
        filter_list = MessageFilter.get_active_filters(filter_dict)
        # Get Message Types
        msg_type = MessageFilter.get_message_type(message_event)
        msg_text = message_event.message.text.lower()
        if msg_type in filter_list:
            logger.info(f'Filter caught :: {msg_type}')
            return True
        if 'contain' not in filter_list and 'notcontain' not in filter_list:
            logger.info('All filters passed.')
            return False
        # Assume message does not contain the required word
        contains_required_word = False
        if 'contain' in filter_list:
            # Look for text messages only
            if message_event.media is not None:
                return False
            keywords = filter_dict['contain'].split('<stop_word>')
            for keyword in keywords:
                if keyword in msg_text:
                    contains_required_word = True
                    break
        # Assume message does contain the blacklist word
        contains_blacklist_word = False
        if 'notcontain' in filter_list:
            # Look for text messages only
            if message_event.media is not None:
                return False
            keywords = filter_dict['notcontain'].split('<stop_word>')
            for keyword in keywords:
                if keyword in msg_text:
                    contains_blacklist_word = True
                    break
        logger.info('Contains word :: {} && Contains Blacklist :: {}'.format(
            contains_required_word, contains_blacklist_word))
        return (not contains_required_word) or contains_blacklist_word

↪️transformation.py

from db.database import Database
# Connect to database
database = Database()
class MessageTransformation:
    @staticmethod
    def get_transformed_msg(message_event, redirection_id):
        transformations = database.get_transformations_of_redirection(
            redirection_id)
        # Apply Transformation
        msg_entities = message_event.message.entities
        msg_txt = message_event.message.message
        for transformation in transformations:
            old_phrase = transformation[2]
            new_phrase = transformation[3]
            msg_txt = msg_txt.replace(old_phrase, new_phrase)
        return msg_txt
if __name__ == "__main__":
    msg = 'hi there. I love violin'
    resp = MessageTransformation.get_transformed_msg(msg, 41)
    print(resp)

↪️Файл Dockerfile

FROM python:3.8-rc-buster
RUN apt-get clean && \
  rm -rf /var/lib/apt/lists/* && \
  apt-get clean && \
  apt-get update
RUN apt-get install python-dev default-libmysqlclient-dev -y
WORKDIR /telegram-python-agent
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "server.py"]

↪️client.py

from telethon import TelegramClient, events, sync
from config import api_id, api_hash
telegram_client = TelegramClient('session/telethon', api_id, api_hash

↪️forwarder.py

import json
import logging
from client import telegram_client
from db.database import Database
from utils.filter import MessageFilter
from utils.transformation import MessageTransformation
logger = logging.getLogger('Forwarder')
database = Database()
async def forwarder_event_handler(event):
    # Ignore Outgoing Message Updates
    if (event.out):
        return
    is_group = event.is_group
    is_channel = event.is_channel
    is_private = event.is_private
    message = event.message.message
    has_media = event.media
    sender_id = None
    if is_channel:
        logger.info('Channel Message Received')
        sender_id = event.message.to_id.channel_id
    elif is_group:
        logger.info('Group Message Received')
        sender_id = event.message.to_id.chat_id
    elif is_private:
        logger.info('Private Message Received')
        sender_id = event.from_id if has_media else event.original_update.user_id
    else:
        logger.info('Invalid Sender Type', event)
    logger.info(f'Message : {message}')
    logger.info(f'Sender : {sender_id}')
    # Mark as read (Optional)
    user_entity = await telegram_client.get_input_entity(sender_id)
    await telegram_client.send_read_acknowledge(user_entity, max_id=event.original_update.pts)
    # Get all Receivers of given userid
    try:
        redirections = database.get_active_redirections_of_source(sender_id)
        logger.info(f"Active Redirections: {json.dumps(redirections)}")
        for redirection in redirections:
            redirection_id = redirection[0]
            author_id = int(redirection[1])
            source = int(redirection[2])
            destination = int(redirection[3])
            if source != sender_id:
                logger.info("Source != Sender")
                continue
            # Get User from database
            user = database.get_user(author_id)
            is_user_premium = user[4]
            # Allow premium users only
            if is_user_premium == 1:
                should_filter = MessageFilter.filter_msg(redirection_id, event)
                if should_filter:
                    return
                if has_media:
                    await telegram_client.send_file(destination, event.media, caption=message)
                else:
                    transformed_message = MessageTransformation.get_transformed_msg(
                        event, redirection_id)
                    await telegram_client.send_message(destination, transformed_message)
            else:
                await telegram_client.forward_messages(destination, event.message.id, sender_id)
            logger.info(f'Message sent to {destination}')
    except Exception as err:
        logger.error(err)

↪️requirements.txt

Quart==0.13.0
Telethon==1.16.2
psycopg2==2.8.5

↪️server.py

mport logging
import telethon
import hypercorn.asyncio
from quart import Quart, request, render_template_string
from telethon import TelegramClient, events
from telethon.tl.functions.messages import ImportChatInviteRequest
from telethon.tl.functions.channels import JoinChannelRequest
from client import telegram_client
from config import API_PORT
from forwarder import forwarder_event_handler
format_str = '[%(levelname)s] %(filename)s:%(lineno)s -- %(message)s'
logging.basicConfig(level=logging.INFO, format=format_str)
# Create Flask application instance
app = Quart(__name__)
@app.route('/')
def index():
    return "Telethon API is up and running"
@app.route('/joinPublicUserEntity')
async def joinPublicUserEntity():
    """
    Join Private Users
    """
    entity = request.args.get('entity')
    app.logger.info('[/joinPublicUserEntity] :: Entity Name {}'.format(entity))
    try:
        msg = '/start'
        result = await telegram_client.get_entity(entity)
        return result.to_dict()
    except Exception as exception:
        return {'error': str(exception)}
@app.route('/joinPublicEntity')
async def joinPublicEntity():
    """
    Join Public Groups & Channels
    """
    entity = request.args.get('entity')
    app.logger.info('[/joinPublicEntity] :: Entity Name {}'.format(entity))
    try:
        result = await telegram_client(JoinChannelRequest(entity))
        return result.chats[0].to_dict()
    except telethon.errors.rpcerrorlist.UserAlreadyParticipantError:
        return {'success': 'ok'}
    except Exception as exception:
        return {'error': str(exception)}
@app.route('/joinPrivateEntity')
async def joinPrivateEntity():
    """
    Join Invation Link
    """
    hash = request.args.get('hash')
    app.logger.info('[/joinPrivateEntity] :: Hash {}'.format(hash))
    try:
        result = await telegram_client(ImportChatInviteRequest(hash))
        return result.chats[0].to_dict()
    except telethon.errors.rpcerrorlist.UserAlreadyParticipantError:
        return {'succes': 'ok'}
    except Exception as exception:
        app.logger.error(exception)
        return {'error': str(exception)}
@app.route('/getentity', methods=['GET'])
async def getEntity():
    entity = request.args.get('entity')
    is_entity_id = request.args.get('is_id') or '0'
    if (is_entity_id == '1'):
        entity = int(entity)
    app.logger.info('[/getentity] :: Entity Name {}'.format(entity))
    try:
        result = await telegram_client.get_entity(entity)
        return result.to_dict()
    except Exception as exception:
        app.logger.error(exception)
        return {'error': str(exception)}
@app.before_serving
async def startup():
    await telegram_client.connect()
    if await telegram_client.is_user_authorized():
        user = await telegram_client.get_me()
        app.logger.info(f'Logged in as @{user.username}')
@app.after_serving
async def cleanup():
    await telegram_client.disconnect()
BASE_TEMPLATE = '''
<!DOCTYPE html>
<html>
    <head>
        <meta charset='UTF-8'>
        <title>Telethon + Quart</title>
    </head>
    <body>{{ content | safe }}</body>
</html>
'''
PHONE_FORM = '''
<form action='/login' method='post'>
    Phone (international format): <input name='phone' type='text' placeholder='+34600000000'>
    <input type='submit'>
</form>
'''
CODE_FORM = '''
<form action='/login' method='post'>
    Telegram code: <input name='code' type='text' placeholder='70707'>
    <input type='submit'>
</form>
'''
phone = None
@app.route('/login', methods=['GET', 'POST'])
async def root():
    # We want to update the global phone variable to remember it
    global phone
    # Check form parameters (phone/code)
    form = await request.form
    if 'phone' in form:
        phone = form['phone']
        await telegram_client.send_code_request(phone)
    if 'code' in form:
        await telegram_client.sign_in(code=form['code'])
    # If we're logged in, show them some messages from their first dialog
    if await telegram_client.is_user_authorized():
        user = await telegram_client.get_me()
        return f'Logged in as @{user.username}'
    # Ask for the phone if we don't know it yet
    if phone is None:
        return await render_template_string(BASE_TEMPLATE, content=PHONE_FORM)
    # We have the phone, but we're not logged in, so ask for the code
    return await render_template_string(BASE_TEMPLATE, content=CODE_FORM)
async def main():
    hypercorn_config = hypercorn.Config()
    hypercorn_config.bind = [f"0.0.0.0:{API_PORT}"]
    await hypercorn.asyncio.serve(app, hypercorn_config)
if __name__ == "__main__":
    telegram_client.on(events.NewMessage)(forwarder_event_handler)
    telegram_client.loop.run_until_complete(main())

✉Бот

✉Конфигурация

↪️index.js

const config = {
  PORT: process.env.PORT || 3000,
  DB_HOST: process.env.DB_HOST || 'localhost',
  AGENT_HOSTNAME: process.env.AGENT_HOSTNAME || 'localhost',
  AGENT_PORT: process.env.AGENT_PORT || 3000,
  TG: {
    TG_API_KEY: process.env.TG_API_KEY,
    TG_BOT_USERNAME: process.env.TG_BOT_USERNAME,
  },
  APP: {
    FREE_USER: {
      QUOTA_LIMIT: 10,
    },
    PREMIUM_USER: {
      QUOTA_LIMIT: 0,
    },
  },
};

✉контроллеры

✉Сообщение

↪️parser.js

const validCommands = [
  '/add',
  '/remove',
  '/list',
  '/activate',
  '/deactivate',
  '/help',
  '/ref',
  '/filters',
  '/filter',
  '/transform',
  '/start',
  '/transforms',
  '/transformrank',
  '/transformremove',
];
// Command Error Constructor Function
const commandError = (command, errorMsg) => {
  return {
    command,
    error: errorMsg,
  };
};
const addMessageEntities = messageEvent => {
  const entities = messageEvent.entities;
  let msgText = messageEvent.text;
  let addedOffset = 0;
  entities.forEach(entity => {
    const entityType = entity.type;
    let offset = entity.offset + addedOffset;
    let length = offset + entity.length;
    if (entityType === 'pre') {
      msgText = msgText.slice(0, offset) + '```' + msgText.slice(offset);
      length += 3;
      msgText = msgText.slice(0, length) + '```' + msgText.slice(length);
      addedOffset += 6;
    } else if (entityType === 'bold') {
      msgText = msgText.slice(0, offset) + '**' + msgText.slice(offset);
      length += 2;
      msgText = msgText.slice(0, length) + '**' + msgText.slice(length);
      addedOffset += 4;
    } else if (entityType === 'italic') {
      msgText = msgText.slice(0, offset) + '__' + msgText.slice(offset);
      length += 2;
      msgText = msgText.slice(0, length) + '__' + msgText.slice(length);
      addedOffset += 4;
    } else if (entityType === 'code') {
      msgText = msgText.slice(0, offset) + '`' + msgText.slice(offset);
      length += 1;
      msgText = msgText.slice(0, length) + '`' + msgText.slice(length);
      addedOffset += 2;
    }
  });
  return msgText;
};
/////////////////////////////
// Transformations Command //
/////////////////////////////
// Remove Transformation
const parseCommandTransformRemove = message => {
  const msgArr = message.trim().split(' ');
  if (msgArr.length !== 2) {
    let errMsg = 'Should contain 1 parameter.\n\n';
    errMsg += '`/transformremove <redirection id>\n`';
    return commandError(msgArr[0], errMsg);
  }
  return {
    transformationId: msgArr[1],
  };
};
// List Transformations
const parseCommandTransforms = message => {
  const msgArr = message.trim().split(' ');
  if (msgArr.length !== 2) {
    let errMsg = 'Should contain 1 parameter.\n\n';
    errMsg += '`/transforms <redirection id>\n`';
    return commandError(msgArr[0], errMsg);
  }
  return {
    redirectionId: msgArr[1],
  };
};
// Add Transformation
const parseCommandTransform = (message, messageEvent) => {
  message = addMessageEntities(messageEvent);
  const msgArrOfLines = message.trim().split('\n');
  const msgArr = msgArrOfLines[0].trim().replace(/\n/g, '').split(' ');
  if (msgArrOfLines.length !== 3 || msgArr.length !== 2) {
    let errMsg = 'Should contain 3 parameters.\n\n';
    errMsg += '`/transform <redirection id>\n`';
    errMsg += '`<phrase to transform>\n<phrase to transform with>`\n\n';
    errMsg += 'Example: \n\n';
    errMsg += '`/transform 1\njustin bieber\nFreddie Mercury`';
    return commandError(msgArr[0], errMsg);
  }
  return {
    redirectionId: msgArr[1],
    oldPhrase: msgArrOfLines[1],
    newPhrase: msgArrOfLines[2],
  };
};
// Swap rank of transformations
const parseCommandTransformRank = message => {
  const msgArr = message.trim().split(' ');
  if (msgArr.length !== 4) {
    let errMsg = 'Should contain 3 parameters.\n\n';
    errMsg += '`/transformrank <redirection id> <rank1> <rank2>\n\n`';
    errMsg += 'Example: \n\n';
    errMsg += '`/transform 100 2 3`';
    return commandError(msgArr[0], errMsg);
  }
  return {
    redirectionId: msgArr[1],
    rank1: msgArr[2],
    rank2: msgArr[3],
  };
};
//////////////////////////
// Redirection commands //
//////////////////////////
// Add Redirection
const parseCommandAdd = message => {
  const msgArr = message.trim().split(' ');
  let errMsg = 'Should contain 2 parameters.\n\n';
  errMsg += '`/add @youtube @google\n/add https://t.me/joinchat @facebook`';
  if (msgArr.length !== 3) return commandError(msgArr[0], errMsg);
  return {
    source: msgArr[1],
    destination: msgArr[2],
  };
};
// Remove Redirection
const parseCommandRemove = message => {
  const msgArr = message.trim().split(' ');
  let errMsg = 'Should contain 1 parameter.\n\n';
  errMsg += '`/remove <redirection id>`';
  if (msgArr.length !== 2) return commandError(msgArr[0], errMsg);
  return { redirectionId: msgArr[1] };
};
// List Redirections
const parseCommandList = message => {
  const msgArr = message.trim().split(' ');
  if (msgArr.length !== 1) return commandError(msgArr[0], 'Should not have any parameter');
  return true;
};
// Activate Redirection
const parseCommandActivate = message => {
  const msgArr = message.trim().split(' ');
  if (msgArr.length !== 2) return commandError(msgArr[0], 'Should contain 1 parameter\n\n`/activate <redirection id>`');
  const redirectionId = msgArr[1];
  if (isNaN(Number(redirectionId))) {
    return commandError(msgArr[0], `redirection id should be a number`);
  }
  return { redirectionId };
};
// Deactivate Redirection
const parseCommandDeactivate = message => {
  const msgArr = message.trim().split(' ');
  if (msgArr.length !== 2) {
    return commandError(msgArr[0], 'Should contain 1 parameter\n\n`/deactivate <redirection id>`');
  }
  const redirectionId = msgArr[1];
  if (isNaN(Number(redirectionId))) {
    return commandError(msgArr[0], `redirection id should be a number`);
  }
  return { redirectionId: msgArr[1] };
};
/////////////////////
// Filter Commands //
/////////////////////
// Add Filter
const parseCommandFilter = message => {
  let parsedResponse = {};
  const msgArr = message.trim().split(' ');
  if (msgArr.length < 4) {
    let reply = 'Should contain at least 3 parameters.\n\n';
    reply += '`/filter <filter name> <redirection id> <filter state>`\n\n';
    reply += 'Example : \n\n';
    reply += '`/filter photo 152 on`';
    return commandError(msgArr[0], reply);
  }
  const validFilters = ['photo', 'video', 'audio', 'sticker', 'document', 'hashtag', 'link', 'contain', 'notcontain'];
  const validStates = ['on', 'off'];
  const filterName = msgArr[1];
  const redirectionId = msgArr[2];
  const filterState = msgArr[3].split(/\n/g)[0];
  ////////////////////
  // Valid filter ? //
  ////////////////////
  if (validFilters.indexOf(filterName) < 0) {
    const errMsg = `Invalid filter name. Available filters :\n\n- ${validFilters.join('\n- ')}`;
    return commandError(msgArr[0], errMsg);
  }
  ///////////////////
  // Valid state ? //
  ///////////////////
  if (validStates.indexOf(filterState.toLowerCase()) < 0) {
    const errMsg = `Invalid State. Available states :\n\n- ${validStates.join('\n- ')}`;
    return commandError(msgArr[0], errMsg);
  }
  /////////////////////////////////////////////////////////////////
  // If filter name is contain or notcontain gather the keywords //
  // Keywords are only required if filterstate = 'on'            //
  /////////////////////////////////////////////////////////////////
  if (filterState === 'on') {
    if (filterName === 'contain' || filterName === 'notcontain') {
      const msgArrLine = message.trim().split('\n');
      const filterKeywords = msgArrLine.splice(1, msgArrLine.length).filter(x => x !== '');
      parsedResponse.keywords = filterKeywords;
      if (filterKeywords.length === 0) {
        let errMsg = 'Please provide keywords.\n\n Example: \n';
        errMsg += '`/filter contain 1 on\nqueen\nBohemian Rhapsody`';
        return commandError(msgArr[0], errMsg);
      }
    }
  }
  parsedResponse.name = filterName;
  parsedResponse.state = filterState;
  parsedResponse.redirectionId = redirectionId;
  return parsedResponse;
};
// List Filters
const parseCommandFilters = message => {
  const msgArr = message.trim().split(' ');
  let errMsg = 'Should contain 1 parameter\n\n';
  errMsg += '`/filters <filter id>`';
  if (msgArr.length !== 2) return commandError(msgArr[0], errMsg);
  return { filterId: msgArr[1] };
};
class MessageParser {
  /**
   * Takes in message and checks if there is a valid command
   * @param {String} message Telegram Message
   * @returns {Boolean}
   */
  static isValidCommand(message) {
    const firstWord = message.trim().split(' ')[0];
    return validCommands.indexOf(firstWord) >= 0;
  }
  static hashMap() {
    return {
      '/add': parseCommandAdd,
      '/remove': parseCommandRemove,
      '/list': parseCommandList,
      '/activate': parseCommandActivate,
      '/deactivate': parseCommandDeactivate,
      '/filter': parseCommandFilter,
      '/filters': parseCommandFilters,
      '/transform': parseCommandTransform,
      '/transforms': parseCommandTransforms,
      '/transformrank': parseCommandTransformRank,
      '/transformremove': parseCommandTransformRemove,
    };
  }
  /**
   * Takes in message and returns command.
   * Should only be called after making sure there is a valid command using isValidCommand()
   * @param {String} message Telegram Message
   * @returns {String} returns command name
   */
  static getCommand(message) {
    return message.trim().split(' ')[0];
  }
}
module.exports = MessageParser;
if (require.main === module) {
  let message = '/list ';
  const isValid = MessageParser.isValidCommand(message);
  if (isValid) {
    const command = MessageParser.getCommand(message);
    const parser = hashMap[command];
    const response = parser(message);
    if (response.error) {
      return console.log(response);
    }
    if (command === '/add') {
      console.log(`Source: ${response.source} && Destination: ${response.destination}`);
    } else if (command === '/remove') {
      console.log(`Redirection ID: ${response.redirectionId}`);
    }
  }
}

↪️activateRedirection.js

const database = require('../db/database');
const ForwardAgent = require('../services/agent');
const { TG_BOT_USERNAME } = require('../config').TG;
/**
 * Activates a redirection
 * @param {String} sender Chat id of the owner
 * @param {String} redirectionId Redirection Id
 */
const activateRedirection = async (sender, redirectionId) => {
  /////////////////////////////////////////////
  // Sender must be the owner of redirection //
  /////////////////////////////////////////////
  const redirections = await database.getRedirections(sender);
  let redirectionOfInterest = redirections.filter(redirection => redirection.id == redirectionId);
  if (redirectionOfInterest.length === 0) {
    throw new Error('Redirection does not exist');
  }
  /////////////////////////////////////////////
  // If destination is Channel or Supergroup //
  // Should have admin rights                //
  /////////////////////////////////////////////
  const destId = redirectionOfInterest[0].destination;
  const entity = await ForwardAgent.getEntity(destId, { is_id: true });
  if (entity.entity.type === 'channel' && entity.entity.megagroup === false) {
    if (!entity.entity.adminRights) {
      const errMsg = `Please provide admin rights to ${TG_BOT_USERNAME} on ${redirectionOfInterest[0].destination_title}`;
      throw new Error(errMsg);
    }
  }
  ////////////////////////
  // Update to database //
  ////////////////////////
  await database.activateRedirection(redirectionId);
};
module.exports = activateRedirection;

↪️addFilter.js

const database = require('../db/database');
/**
 * Activates a redirection
 * @param {String} sender Chat id of the owner
 * @param {String} filterData.redirectionId Redirection Id
 * @param {String} filterData.name Filter name
 * @param {String} filterData.state Filter state
 * @param {Array} filterData.keywords Keywords if filter is contain or notcontain
 * @typedef {Object} AddFilterResponse
 * @property {string} error - Error Message
 * @property {Object} filterData - Filter Object
 * @returns {AddFilterResponse}
 */
const addFilter = async (sender, filterData) => {
  /////////////////////////////////////////////
  // Sender must be the owner of redirection //
  /////////////////////////////////////////////
  const redirections = await database.getRedirections(sender);
  let redirectionOfInterest = redirections.filter(redirection => redirection.id == filterData.redirectionId);
  if (redirectionOfInterest.length === 0) return { error: '⚠ Redirection doesnot exist' };
  ////////////////////////////////////////////
  // If Filtername is contain or notcontain //
  ////////////////////////////////////////////
  if (filterData.name === 'contain' || filterData.name === 'notcontain') {
    const keywords = filterData.keywords ? filterData.keywords.join('<stop_word>') : null;
    await database.saveFilter(filterData.redirectionId, filterData.name, keywords);
    return { filterData };
  } else {
    const filterState = filterData.state === 'on' ? 1 : 0;
    await database.saveFilter(filterData.redirectionId, filterData.name, filterState);
    return { filterData };
  }
};
module.exports = addFilter;
if (require.main === module) {
  addFilter('451722605', {
    redirectionId: 41,
    state: 'on',
    keywords: ['hi', 'there', 'how are you ?'],
    name: 'contain',
  })
    .then(x => console.log(x))
    .catch(x => console.log(x));
}

↪️addRedirection.js

const database = require('../db/database');
const ForwardAgent = require('../services/agent');
const QUOTA_LIMIT = require('../config/').APP.FREE_USER.QUOTA_LIMIT;
/**
 * Verifies that the source/destination is in one of the two valid formats
 * -- If it's a public entity (username), it should start with "@"
 * -- If it's a private entity (invitation link), it should start with t.me/joinchat/<HASH>
 * @param {string} entity username or invitation link
 * @returns {Object}
 */
const checkSourcePattern = entity => {
  if (entity.indexOf('@') === 0) return { username: entity };
  if (entity.indexOf('t.me/joinchat/') === 0) return { hash: entity.replace('t.me/joinchat/', '') };
  if (entity.indexOf('https://t.me/joinchat/') === 0) return { hash: entity.replace('https://t.me/joinchat/', '') };
  throw new Error('Invalid format');
};
/**
 * Adds a redirection
 * @param {String} sender Chat id of the owner
 * @param {String} source Username / Link of Source
 * @param {String} destination Username / Link of Destination
 */
const addRedirection = async (sender, source, destination) => {
  /////////////////////////////////////////////////
  // Validate and get Source && Destination type //
  /////////////////////////////////////////////////
  let sourceType, destinationType;
  try {
    sourceType = checkSourcePattern(source);
    destinationType = checkSourcePattern(destination);
  } catch (error) {
    console.log(error);
    if (error) return { error: error.message };
  }
  /////////////////
  // Quota Check //
  /////////////////
  const userRecord = await database.getUser(sender);
  if (!userRecord) {
    return {
      error: 'You are not registered. Please send /start to register',
    };
  }
  const userIsPremium = userRecord.premium == '1' ? true : false;
  if (!userIsPremium) {
    if (userRecord.quota >= QUOTA_LIMIT) {
      return {
        error: 'You have reached your quota limit. Please upgrade your account.',
      };
    }
  }
  ///////////////////////////////////////
  // Get Entities                      //
  // If not joinable, will throw Error //
  ///////////////////////////////////////
  let sourceEntity = await ForwardAgent.getEntity(source);
  let destinationEntity = await ForwardAgent.getEntity(destination);
  //////////////////////////
  // Join agent to source //
  //////////////////////////
  let joinSrcRequestResponse = null;
  if (sourceType.username) {
    if (sourceEntity.entity.type === 'user') {
      joinSrcRequestResponse = await ForwardAgent.joinPublicUserEntity(sourceType.username);
    } else {
      joinSrcRequestResponse = await ForwardAgent.joinPublicEntity(sourceType.username);
    }
  } else if (sourceType.hash) {
    joinSrcRequestResponse = await ForwardAgent.joinPrivateEntity(sourceType.hash);
  }
  if (joinSrcRequestResponse.error) return { error: joinSrcRequestResponse.error };
  ///////////////////////////////
  // Join agent to destination //
  ///////////////////////////////
  let joinDestRequestResponse = null;
  if (destinationType.username) {
    if (destinationEntity.entity.type === 'user') {
      joinDestRequestResponse = await ForwardAgent.joinPublicUserEntity(destinationType.username);
    } else {
      joinDestRequestResponse = await ForwardAgent.joinPublicEntity(destinationType.username);
    }
  } else if (destinationType.hash) {
    joinDestRequestResponse = await ForwardAgent.joinPrivateEntity(destinationType.hash);
  }
  if (joinDestRequestResponse.error) return { error: joinDestRequestResponse.error };
  /////////////////////////////////////////////////////////////////
  // We cannot get entity from an invitation link before joining //
  // Now that we have joined, we can get the entity              //
  /////////////////////////////////////////////////////////////////
  if (sourceEntity.entity === null) {
    sourceEntity = await ForwardAgent.getEntity(source);
  }
  if (destinationEntity.entity === null) {
    destinationEntity = await ForwardAgent.getEntity(destination);
  }
  //////////////////////////
  // No Duplicate Entries //
  // No Circular Entries  //
  //////////////////////////
  const allRedirections = await database.getRedirections(sender);
  for (const redirection of allRedirections) {
    const source = redirection.source;
    const destination = redirection.destination;
    if (source == sourceEntity.entity.chatId && destination == destinationEntity.entity.chatId) {
      return { error: `Redirection already exists with id <code>[${redirection.id}]</code> ` };
    }
    if (source == destinationEntity.entity.chatId && destination == sourceEntity.entity.chatId) {
      return { error: `Circular redirection is not allowed <code>[${redirection.id}]</code>` };
    }
  }
  ///////////////////////
  // Store to database //
  // Increase Quota    //
  ///////////////////////
  const srcId = sourceEntity.entity.chatId;
  const destId = destinationEntity.entity.chatId;
  const srcTitle = sourceEntity.entity.title;
  const destTitle = destinationEntity.entity.title;
  await database.saveRedirection(sender, srcId, destId, srcTitle, destTitle);
  await database.changeUserQuota(sender);
  return { success: true };
};
module.exports = addRedirection;

↪️addTransformation.js

const pino = require('pino');
const database = require('../db/database');
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });
const addTransformation = async (sender, redirectionId, oldPhrase, newPhrase) => {
  /////////////////////////////////////////////
  // Sender must be the owner of redirection //
  /////////////////////////////////////////////
  const redirections = await database.getRedirections(sender);
  let redirectionOfInterest = redirections.filter(redirection => redirection.id == redirectionId);
  if (redirectionOfInterest.length === 0) throw Error('Redirection does not exist');
  /////////////////////////////////////////////////
  // Fetch Transformations for given redirection //
  // To determine the rank of transformation     //
  /////////////////////////////////////////////////
  const transformations = await database.getTransformationsOfRedirection(redirectionId);
  const currentHighestRank = transformations.reduce((prevVal, curVal, curIndex) => {
    return curVal.rank > prevVal ? curVal.rank : prevVal;
  }, -1);
  const rankOfNewTransformation = currentHighestRank + 1;
  /////////////////////
  // Update database //
  /////////////////////
  console.log({ redirectionId, oldPhrase, newPhrase, rankOfNewTransformation, sender });
  const dbResponse = await database.saveTransformation(redirectionId, oldPhrase, newPhrase, rankOfNewTransformation);
  return { redirectionId, transformationId: dbResponse.rows[0].id };
};
module.exports = addTransformation;

↪️отключитеRedirection.js

const database = require('../db/database');
/**
 * Activates a redirection
 * @param {String} sender Chat id of the owner
 * @param {String} redirectionId Redirection Id
 */
const deactivateRedirection = (sender, redirectionId) => {
  return new Promise(async (resolve, reject) => {
    try {
      /////////////////////////////////////////////
      // Sender must be the owner of redirection //
      /////////////////////////////////////////////
      const redirections = await database.getRedirections(sender);
      let redirectionOfInterest = redirections.filter((redirection) => redirection.id == redirectionId)
      if (redirectionOfInterest.length === 0) throw Error('Redirection doesnot exist');
      ////////////////////////
      // Update to database //
      ////////////////////////
      const dbResponse = await database.deactivateRedirection(redirectionId);
      return resolve({ redirection: redirectionOfInterest[0], dbResponse });
    } catch (err) {
      reject(err);
    }
  })
}
module.exports = deactivateRedirection;
if (require.main === module) {
  deactivateRedirection('451722605', '39')
    .then(x => console.log(x))
    .catch(x => console.log(x))
}

↪️getFilter.js

const database = require('../db/database');
/**
 * @param {String} sender Sender Chat id
 * @param {String} filterId Filter Id
 * @typedef {Object} GetFilterResponse
 * @property {string} error - Error Message
 * @property {Object} filter - Filter Object
 * @returns {GetFilterResponse}
 */
const getFilter = async (sender, filterId) => {
  /////////////////////////////////////////////
  // Sender must be the owner of redirection //
  // Linked with the given filter            //
  /////////////////////////////////////////////
  const redirections = await database.getRedirections(sender);
  const redirectionOfInterest = redirections.filter(redirection => redirection.id == filterId);
  if (redirectionOfInterest.length === 0) return { error: '⚠ Filter does not exist' };
  ////////////////
  // Get Filter //
  ////////////////
  const filter = await database.getFilter(filterId);
  if (!filter) return { error: '⚠ You have no filters' };
  return { filter };
};
module.exports = getFilter;

↪️getTransformations.js

const database = require('../db/database');
const getTransformations = async (sender, redirectionId) => {
  /////////////////////////////////////////////
  // Sender must be the owner of redirection //
  // Linked with the given transformations   //
  /////////////////////////////////////////////
  const redirections = await database.getRedirections(sender);
  const redirectionOfInterest = redirections.filter(redirection => redirection.id == redirectionId);
  if (redirectionOfInterest.length === 0) throw Error('Redirection does not exist');
  /////////////////////////
  // Get transformations //
  /////////////////////////
  return await database.getTransformationsOfRedirection(redirectionId);
};
module.exports = getTransformations;

↪️removeRedirection.js

const database = require('../db/database');
const removeRedirection = async (sender, redirectionId) => {
  return new Promise(async (resolve, reject) => {
    try {
      /////////////////////////////////////////////
      // Sender must be the owner of redirection //
      /////////////////////////////////////////////
      const redirections = await database.getRedirections(sender);
      let redirectionOfInterest = redirections.filter((redirection) => redirection.id == redirectionId)
      if (redirectionOfInterest.length === 0) throw Error('Redirection does not exist');
      ////////////////////////
      // Update to database //
      ////////////////////////
      const dbResponse = await database.removeRedirection(redirectionId);
      await database.changeUserQuota(sender, -1);
      return resolve({ dbResponse });
    } catch (err) {
      console.log(`[Error removeRedirection.js] :: ${err}`);
      return reject(err);
    }
  })
}
module.exports = removeRedirection;

↪️removeTransformation.js

const database = require('../db/database');
const removeTransformation = (sender, transformationId) => {
  return new Promise(async (resolve, reject) => {
    try {
      /////////////////////////////////////////////
      // Sender must be the owner of redirection //
      // Linked with the given transformations   //
      /////////////////////////////////////////////
      const transformation = await database.getTransformation(transformationId);
      if (transformation === undefined) throw new Error('Transformation does not exist');
      const redirections = await database.getRedirections(sender);
      const isOwner = redirections.filter(r => r.id == transformation['redirection_id']).length > 0;
      if (!isOwner) throw new Error('You are not the owner of the transformation.');
      ///////////////////////////
      // Delete transformation //
      ///////////////////////////
      await database.removeTransformation(transformationId);
      return resolve({ transformationId });
    } catch (err) {
      console.log(`[ERROR removeTransformation.js] : ${err}`);
      return reject(err);
    }
  });
};
module.exports = removeTransformation;

↪️swapTransformationRank.js

const database = require('../db/database');
const swapTransformationRank = (sender, redirectionId, rank1, rank2) => {
  return new Promise( async(resolve, reject) => {
    try {
      /////////////////////////////////////////////
      // Sender must be the owner of redirection //
      /////////////////////////////////////////////
      const redirections = await database.getRedirections(sender);
      let redirectionOfInterest = redirections.filter((redirection) => redirection.id == redirectionId)
      if (redirectionOfInterest.length === 0) throw Error('Redirection doesnot exist');
      /////////////////////////////////////////////
      // Both transformation ranks should exists //
      /////////////////////////////////////////////
      const transformations = await database.getTransformationsOfRedirection(redirectionId);
      const requiredTransformations = transformations.filter((tranformation) => {
        return tranformation.rank == rank1 || tranformation.rank == rank2;
      });
      if (requiredTransformations.length !== 2) throw new Error('The transformation does not exist');
      ////////////////
      // Swap ranks //
      ////////////////
      await database.changeTransformationRank(requiredTransformations[0].id, requiredTransformations[1].rank);
      await database.changeTransformationRank(requiredTransformations[1].id, requiredTransformations[0].rank);
      return resolve({ redirectionId })
    } catch (err) {
      console.log(`ERROR: [swapTransformationRank()] ${err}`)
      reject(err);
    }
  })
}
module.exports = swapTransformationRank;

✉дб

↪️database.js

const knex = require('../services/database');
class Database {
  constructor() {}
  ///////////
  // USERS //
  ///////////
  getUser(chatId) {
    return knex('users').select('*').where({ chat_id: chatId }).first();
  }
  getAllUsers() {
    return knex('users').select('*');
  }
  saveUser(chatId, username, refCode) {
    return knex('users').insert({ chat_id: chatId, username: username, ref_code: refCode });
  }
  changeUserQuota(chatId, change = 1) {
    const sql = `UPDATE users SET quota = quota + ? WHERE chat_id = ?`;
    return knex.raw(sql, [change, chatId]);
  }
  getUserQuota(chatId) {
    return knex('user').select('quote').where({ chat_id: chatId });
  }
  //////////////////
  // REDIRECTIONS //
  //////////////////
  getRedirections(userId) {
    return knex('redirections').select('*').where({ owner: userId });
  }
  saveRedirection(owner, source, destination, srcTitle, destTitle) {
    return knex('redirections').insert({
      owner,
      source,
      destination,
      source_title: srcTitle,
      destination_title: destTitle,
    });
  }
  removeRedirection(redirectionId) {
    return knex.raw('DELETE FROM redirections WHERE id = ?', [redirectionId]);
  }
  activateRedirection(redirectionId) {
    return knex.raw('UPDATE redirections SET active = ? WHERE id = ?', [1, redirectionId]);
  }
  deactivateRedirection(redirectionId) {
    return knex.raw('UPDATE redirections SET active = ? WHERE id = ?', [0, redirectionId]);
  }
  /////////////
  // FILTERS //
  /////////////
  /**
   * @param {Number} redirectionId
   * @param {String} filterName One of specific filter names
   * @param {} data filter state or filter keywords
   */
  saveFilter(redirectionId, filterName, data) {
    return knex.raw(
      `INSERT INTO filters (id, ${filterName}) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET ${filterName} = ?`,
      [redirectionId, data, data]
    );
  }
  getFilter(redirectionId) {
    return knex('filters').select('*').where({ id: redirectionId }).first();
  }
  /////////////////////
  // Transformations //
  /////////////////////
  saveTransformation(redirectionId, oldPhrase, newPhrase, rank) {
    return knex.raw(
      `INSERT INTO transformations (redirection_id, old_phrase, new_phrase, rank) VALUES (?, ?, ?, ?) RETURNING id`,
      [redirectionId, oldPhrase, newPhrase, rank]
    );
  }
  getTransformation(transformationId) {
    return knex('transformations').select('*').where({ id: transformationId }).first();
  }
  getTransformationsOfRedirection(redirectionId) {
    return knex('transformations').select('*').where({ redirection_id: redirectionId });
  }
  changeTransformationRank(transformationId, newRank) {
    return new Promise((resolve, reject) => {
      const sql = 'UPDATE transformations SET rank = ? Where id = ?';
      this.connection.query(sql, [newRank, transformationId], (error, results) => {
        if (error) return reject(error);
        resolve(results);
      });
    });
  }
  removeTransformation(transformationId) {
    return knex.raw('DELETE FROM transformations WHERE id = ?', [transformationId]);
  }
}
// Single Ton
module.exports = new Database();
if (require.main === module) {
  const db = new Database();
  // db.changeUserQuota('xxx', 100).then(console.log).catch(console.error);
  // db.getUser('xxx').then(console.log);
}

✉Услуги

↪️agent.js

* An agent that communicates with the python rest api */
const request = require('request');
const { AGENT_HOSTNAME, AGENT_PORT } = require('../config');
const AGENT_URL = `http://${AGENT_HOSTNAME}:${AGENT_PORT}/`;
const _sendRequest = (endpoint) => {
  return new Promise((resolve, reject) => {
    request(AGENT_URL + endpoint, { json: true }, (err, resp, body) => {
      if (err) return reject(err);
      return resolve(body);
    });
  });
};
class ForwardAgent {
  static async joinPublicUserEntity(entityName) {
    const endpoint = `joinPublicUserEntity?entity=${entityName}`;
    try {
      const resp = await _sendRequest(endpoint);
      return resp;
    } catch (err) {
      return err;
    }
  }
  static async joinPublicEntity(entityName) {
    const endpoint = `joinPublicEntity?entity=${entityName}`;
    try {
      const resp = await _sendRequest(endpoint);
      return resp;
    } catch (err) {
      return err;
    }
  }
  static async joinPrivateEntity(hash) {
    const endpoint = `joinPrivateEntity?hash=${hash}`;
    try {
      const resp = await _sendRequest(endpoint);
      return resp;
    } catch (err) {
      return err;
    }
  }
  /**
   * Calls Agent to get the detail of the entity
   * @param {String} entity
   * @returns {Promise} Returns a promise of object
   */
  static getEntity(entity, option = { is_id: false }) {
    return new Promise(async (resolve, reject) => {
      try {
        let endpoint = `getentity?entity=${entity}`;
        if (option.is_id) {
          endpoint += `&is_id=1`;
        }
        const resp = await _sendRequest(endpoint);
        if (resp._ === 'User') {
          return resolve({
            joined: false,
            entity: {
              type: 'user',
              chatId: resp.id,
              title: resp.username || resp.first_name || resp.last_name,
              accessHash: resp.access_hash,
              bot: resp.bot,
            },
          });
        } else if (resp._ === 'Channel') {
          return resolve({
            joined: !resp.left,
            entity: {
              type: 'channel',
              chatId: resp.id,
              title: resp.title || resp.username,
              accessHash: resp.access_hash,
              megagroup: resp.megagroup,
              adminRights: resp.admin_rights,
            },
          });
        } else if (resp._ === 'Chat') {
          if (resp.kicked) {
            throw new Error(`Bot was kicked from ${resp.title}`);
          }
          return resolve({
            joined: !resp.left,
            entity: {
              type: 'group',
              chatId: resp.id,
              title: resp.title,
              accessHash: resp.id,
            },
          });
        } else if (
          resp.error ===
          'Cannot get entity from a channel (or group) that you are not part of. Join the group and retry'
        ) {
          return resolve({
            joined: false,
            entity: null,
          });
        } else {
          throw new Error(resp.error);
        }
      } catch (err) {
        console.error({ err });
        return reject(err);
      }
    });
  }
}
module.exports = ForwardAgent;
if (require.main === module) {
  ForwardAgent.getEntity('1256091383', { is_id: true })
    .then((x) => console.log(x))
    .catch((x) => console.log(x));
}

↪️database.js

const { DB_HOST } = require('../config');
const knex = require('knex')({
  client: 'pg',
  connection: {
    host: DB_HOST,
    user: 'postgres',
    password: 'mysecretpassword',
    database: 'telegram',
  },
});
(async () => {
  // Create Tables
  const hasUserTable = await knex.schema.hasTable('users');
  if (!hasUserTable) {
    await knex.schema.createTable('users', tableBuilder => {
      tableBuilder.string('chat_id').primary();
      tableBuilder.string('username').nullable().unique();
      tableBuilder.string('ref_code').notNullable().unique();
      tableBuilder.string('ref_by');
      tableBuilder.boolean('premium').defaultTo(true);
      tableBuilder.integer('quota').defaultTo(0);
      // Foreign Key
      tableBuilder.foreign('ref_by').references('ref_code').inTable('users');
    });
    console.log('User table created');
  }
  const hasRedTable = await knex.schema.hasTable('redirections');
  if (!hasRedTable) {
    await knex.schema.createTable('redirections', tableBuilder => {
      tableBuilder.increments('id').primary();
      tableBuilder.string('owner').notNullable();
      tableBuilder.string('source').notNullable();
      tableBuilder.string('destination').notNullable();
      tableBuilder.string('source_title').notNullable();
      tableBuilder.string('destination_title').notNullable();
      tableBuilder.boolean('active').defaultTo(false);
      // Foreign Key
      tableBuilder.foreign('owner', 'redirections_fk0').references('chat_id').inTable('users').onDelete('cascade');
    });
    console.log('redirections table created');
  }
  const hasFiltersTable = await knex.schema.hasTable('filters');
  if (!hasFiltersTable) {
    await knex.schema.createTable('filters', tableBuilder => {
      tableBuilder.increments('id').primary();
      tableBuilder.boolean('audio').defaultTo(false);
      tableBuilder.boolean('video').defaultTo(false);
      tableBuilder.boolean('photo').defaultTo(false);
      tableBuilder.boolean('sticker').defaultTo(false);
      tableBuilder.boolean('document').defaultTo(false);
      tableBuilder.boolean('hashtag').defaultTo(false);
      tableBuilder.boolean('link').defaultTo(false);
      tableBuilder.string('contain');
      tableBuilder.string('notcontain');
      // Foreign Key
      tableBuilder.foreign('id').references('id').inTable('redirections').onDelete('cascade');
    });
    console.log('filters table created');
  }
  const hasTransformationsTable = await knex.schema.hasTable('transformations');
  if (!hasTransformationsTable) {
    await knex.schema.createTable('transformations', tableBuilder => {
      tableBuilder.increments('id').primary();
      tableBuilder.integer('redirection_id').notNullable();
      tableBuilder.string('old_phrase').notNullable();
      tableBuilder.string('new_phrase').notNullable();
      tableBuilder.integer('rank').notNullable();
      // Foreign Key
      tableBuilder.foreign('redirection_id').references('id').inTable('redirections').onDelete('cascade');
    });
    console.log('transformations table created');
  }
})();
module.exports = knex;

↪️telegram.js

const TelegramBot = require('node-telegram-bot-api');
const config = require('../config');
const bot = new TelegramBot(config.TG.TG_API_KEY, { polling: false });
module.exports = bot;

↪️.красивее .

{
  "singleQuote": true,
  "tabWidth": 2,
  "semi": true,
  "trailingComma": "es5",
  "printWidth": 120,
  "arrowParens": "avoid"
}

↪️Файл Dockerfile

FROM node:lts-jessie
RUN apt-get update
RUN apt-get install -y netcat
WORKDIR /usr/src/telegram-forwarder/bot
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "bot.js"]

↪️bot.js

// @ts-check
const pino = require('pino');
const { v4: uuidv4 } = require('uuid');
const EventEmitter = require('events').EventEmitter;
const db = require('./db/database');
const bot = require('./services/telegram');
const MessageParser = require('./controllers/message/parser');
// Controllers
const addFilter = require('./controllers/addFilter');
const getFilter = require('./controllers/getFilter');
const addRedirection = require('./controllers/addRedirection');
const activateRedirection = require('./controllers/activateRedirection');
const removeRedirection = require('./controllers/removeRedirection');
const deactivateRedirection = require('./controllers/deactivateRedirection');
const addTransformation = require('./controllers/addTransformation');
// const swapTransformationRank = require('../swapTransformationRank');
const getTransformations = require('./controllers/getTransformations');
const removeTransformation = require('./controllers/removeTransformation');
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });
class CommandHandler extends EventEmitter {}
const commandHandler = new CommandHandler();
bot.onText(new RegExp('^/start#39;), async msgEvent => {
  let reply = 'Welcome to MultiFeed Bot! 🔥\n\n';
  reply += 'Send /help to get usage instructions';
  bot.sendMessage(msgEvent.chat.id, reply).catch(logger.error);
  // Store User to Database
  try {
    await db.saveUser(msgEvent.chat.id, msgEvent.from.username, uuidv4());
  } catch (err) {
    logger.error(err);
  }
});
bot.onText(new RegExp('^/help#39;), msgEvent => {
  let reply = 'Typical workflow in the bot:\n\n';
  reply += '1. You have two links:\n';
  reply += '- `SOURCE` - link to the channel to forward messages FROM';
  reply += '(no admin permissions required)\n';
  reply += '- `DESTINATION` - link to the channel to forward messages TO';
  reply += '(you can add new admins there)\n\n';
  reply += '2. You use `/add` command to create a new redirection from ';
  reply += '`SOURCE` channel to your `DESTINATION` channel\n\n';
  reply += '3. You give posting permissions in `DESTINATION` channel TO THE ';
  reply += 'ACCOUNT that was specified after successful execution of step #2';
  reply += '\n\n';
  reply += '4. You activate the newly created redirection using `/activate` ';
  reply += 'command\n\n';
  reply += 'Having all 4 steps completed, you can enjoy automatic messages ';
  reply += 'forwarding from `SOURCE` to `DESTINATION` 🔥';
  return bot
    .sendMessage(msgEvent.chat.id, reply, {
      parse_mode: 'Markdown',
    })
    .catch(console.error);
});
bot.on('message', msgEvent => {
  if (msgEvent.chat.type == 'private') {
    // Parse Command
    // Check Commands with MessageParser
    const isValidCommand = MessageParser.isValidCommand(msgEvent.text);
    if (!isValidCommand) {
      const reply = '❌ Command does not exist.\n\nType /help';
      return bot.sendMessage(msgEvent.chat.id, reply).catch(console.error);
    }
    const command = MessageParser.getCommand(msgEvent.text);
    // Commands that are handled elsewhere
    const reservedCommands = ['/help', '/start'];
    if (reservedCommands.includes(command)) return;
    const parser = MessageParser.hashMap()[command];
    const parsedMsg = parser(msgEvent.text, msgEvent);
    if (parsedMsg.error) {
      const reply = `❌ Error in command : ${parsedMsg.command}\n\n**${parsedMsg.error}**`;
      return bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: 'Markdown' }).catch(console.error);
    }
    commandHandler.emit(command, parsedMsg, msgEvent);
  }
});
commandHandler.on('/add', async (data, msgEvent) => {
  console.log('Adding redirection');
  try {
    const { error } = await addRedirection(msgEvent.chat.id, data.source, data.destination);
    if (error) {
      return bot
        .sendMessage(msgEvent.chat.id, error, {
          parse_mode: 'HTML',
        })
        .catch(e => console.error(e.message));
    }
    const reply = `✔ New Redirection added`;
    return bot.sendMessage(msgEvent.chat.id, reply, {
      parse_mode: 'HTML',
    });
  } catch (err) {
    console.error(err);
    const reply = err.message || err || 'Some error occured';
    bot
      .sendMessage(msgEvent.chat.id, '❌ ' + reply, {
        parse_mode: 'HTML',
      })
      .catch(console.error);
  }
});
commandHandler.on('/activate', async (data, msgEvent) => {
  console.log('Activating redirection');
  try {
    await activateRedirection(msgEvent.chat.id, data.redirectionId);
    const reply = `Redirection activated <code>[${data.redirectionId}]</code>`;
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: 'HTML' }).catch(console.error);
  } catch (err) {
    const reply = err.message || err || 'Some error occured';
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: 'HTML' }).catch(console.error);
  }
});
commandHandler.on('/list', async (data, msgEvent) => {
  try {
    const redirections = await db.getRedirections(msgEvent.chat.id);
    if (redirections.length === 0) {
      return bot
        .sendMessage(msgEvent.chat.id, 'You have no redirections', { parse_mode: 'HTML' })
        .catch(err => console.log(err));
    }
    let reply = '';
    redirections.forEach(redirection => {
      let state = redirection.active == 1 ? '🔵' : '🔴';
      reply += `--- ${state} <code>[${redirection.id}]</code> ${redirection.source_title} => ${redirection.destination_title}\n`;
    });
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: 'HTML' }).catch(err => console.log(err));
  } catch (err) {
    console.log(err);
    bot.sendMessage(msgEvent.chat.id, err, { parse_mode: 'HTML' });
  }
});
commandHandler.on('/deactivate', async (data, msgEvent) => {
  try {
    await deactivateRedirection(msgEvent.chat.id, data.redirectionId);
    const reply = `Redirection deactivated <code>[${data.redirectionId}]</code>`;
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: 'HTML' }).catch(console.error);
  } catch (err) {
    const reply = err.message || err || 'Some error occured';
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: 'HTML' }).catch(console.error);
  }
});
commandHandler.on('/remove', async (data, msgEvent) => {
  try {
    await removeRedirection(msgEvent.chat.id, data.redirectionId);
    const reply = `Redirection removed <code>[${data.redirectionId}]</code>`;
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: 'HTML' }).catch(console.error);
  } catch (err) {
    const reply = err.message || err || 'Some error occured';
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: 'HTML' }).catch(console.error);
  }
});
commandHandler.on('/filter', async (data, msgEvent) => {
  try {
    const { error, filterData } = await addFilter(msgEvent.chat.id, data);
    if (error) {
      bot.sendMessage(msgEvent.chat.id, error).catch(logger.error);
      return;
    }
    let reply = `✅ Command Success.\n\n<code>`;
    reply += `- Redirection id : [${filterData.redirectionId}]\n`;
    reply += `- Filter Name : ${filterData.name}\n`;
    reply += `- Filter State : ${filterData.state}</code>`;
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: 'HTML' }).catch(console.error);
  } catch (err) {
    const reply = err.message || err || 'Some error occured';
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: 'HTML' }).catch(console.error);
  }
});
commandHandler.on('/filters', async (data, msgEvent) => {
  try {
    const { filter, error } = await getFilter(msgEvent.chat.id, data.filterId);
    if (error) {
      bot.sendMessage(msgEvent.chat.id, error, { parse_mode: 'HTML' }).catch(err => logger.error(err));
      return;
    }
    let reply = `✅ Filters for redirection <code>[${filter.id}]</code>\n\n`;
    reply += '<code>';
    reply += `- ${filter.audio ? '🔵' : '🔴'} audio\n`;
    reply += `- ${filter.video ? '🔵' : '🔴'} video\n`;
    reply += `- ${filter.photo ? '🔵' : '🔴'} photo\n`;
    reply += `- ${filter.sticker ? '🔵' : '🔴'} sticker\n`;
    reply += `- ${filter.document ? '🔵' : '🔴'} document\n`;
    reply += `- ${filter.hashtag ? '🔵' : '🔴'} hashtag\n`;
    reply += `- ${filter.link ? '🔵' : '🔴'} link\n`;
    reply += `- ${filter.contain ? '🔵' : '🔴'} contain = ${
      filter.contain ? filter.contain.replace(/<stop_word>/g, ', ') : null
    }\n`;
    reply += `- ${filter.notcontain ? '🔵' : '🔴'} notcontain = ${
      filter.notcontain ? filter.notcontain.replace('<stop_word>', ', ') : null
    }`;
    reply += '</code>';
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: 'HTML' }).catch(err => logger.error(err));
  } catch (err) {
    logger.error(err);
    bot.sendMessage(msgEvent.chat.id, '❌ Some error occured').catch(err => logger.error(err));
  }
});
commandHandler.on('/transform', async (data, msgEvent) => {
  try {
    const response = await addTransformation(msgEvent.chat.id, data.redirectionId, data.oldPhrase, data.newPhrase);
    const reply = `New transformation added with id <code>${response.transformationId}</code>`;
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: 'HTML' }).catch(err => logger.error(err));
  } catch (err) {
    logger.error(err);
    bot.sendMessage(msgEvent.chat.id, `❌ ${err}`).catch(err => logger.error(err));
  }
});
commandHandler.on('/transforms', async (data, msgEvent) => {
  try {
    const transformations = await getTransformations(msgEvent.chat.id, data.redirectionId);
    if (transformations.length == 0) {
      const reply = 'No transformation found.';
      bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: 'HTML' }).catch(err => logger.error(err));
    } else {
      let reply = `Transformations for redirection [<code>${data.redirectionId}</code>]\n\n`;
      reply += '<b>ID | Rank | Old Phrase | New Phrase</b>\n';
      transformations.forEach(transformation => {
        reply += `<code>${transformation.id}. [${transformation.rank}] ${transformation.old_phrase} ==> ${transformation.new_phrase}</code>\n`;
      });
      bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: 'HTML' }).catch(err => logger.error(err));
    }
  } catch (err) {
    logger.error(err);
    bot.sendMessage(msgEvent.chat.id, `❌ ${err}`).catch(err => logger.error(err));
  }
});
commandHandler.on('/transformremove', async (data, msgEvent) => {
  try {
    await removeTransformation(msgEvent.chat.id, data.transformationId);
    const reply = `✔ Transformation removed <code>${data.transformationId}</code>`;
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: 'HTML' }).catch(err => logger.error(err));
  } catch (err) {
    logger.error(err);
    bot.sendMessage(msgEvent.chat.id, `❌ ${err}`).catch(err => logger.error(err));
  }
});
//   } else if (command === '/transformrank') {
//     try {
//       await swapTransformationRank(
//         sender,
//         parsedMsg.redirectionId,
//         parsedMsg.rank1,
//         parsedMsg.rank2
//       );
//       let reply = `Transformation rank swapped for redirection id \`${parsedMsg.redirectionId}\`\n\n`;
//       reply += `\`${parsedMsg.rank1} <==> ${parsedMsg.rank2}\``;
//       bot
//         .send_message(sender, reply, 'markdown')
//         .catch(err => console.log(err));
//     } catch (err) {
//       const reply = err.message || err || 'Some error occured';
//       bot.send_message(sender, reply).catch(err => console.log(err));
//     }
if (require.main === module) {
  bot.on('message', msg => logger.info(msg));
  bot.on('polling_error', err => logger.error(err));
  bot.startPolling();
}

↪️package-lock.json

{
  "name": "telegram-forwarder-bot-agent",
  "version": "1.0.0",
  "lockfileVersion": 1,
  "requires": true,
  "dependencies": {
    "@sindresorhus/is": {
      "version": "0.14.0",
      "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
      "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==",
      "dev": true
    },
    "@szmarczak/http-timer": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz",
      "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==",
      "dev": true,
      "requires": {
        "defer-to-connect": "^1.0.1"
      }
    },
    "@types/color-name": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
      "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
      "dev": true
    },
    "abbrev": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
      "dev": true
    },
    "ajv": {
      "version": "6.12.4",
      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz",
      "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==",
      "requires": {
        "fast-deep-equal": "^3.1.1",
        "fast-json-stable-stringify": "^2.0.0",
        "json-schema-traverse": "^0.4.1",
        "uri-js": "^4.2.2"
      }
    },
    "ansi-align": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz",
      "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==",
      "dev": true,
      "requires": {
        "string-width": "^3.0.0"
      },
      "dependencies": {
        "string-width": {
          "version": "3.1.0",
          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
          "dev": true,
          "requires": {
            "emoji-regex": "^7.0.1",
            "is-fullwidth-code-point": "^2.0.0",
            "strip-ansi": "^5.1.0"
          }
        }
      }
    },
    "ansi-regex": {
      "version": "4.1.0",
      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
      "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
      "dev": true
    },
    "ansi-styles": {
      "version": "4.2.1",
      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
      "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
      "dev": true,
      "requires": {
        "@types/color-name": "^1.1.1",
        "color-convert": "^2.0.1"
      }
    },
    "anymatch": {
      "version": "3.1.1",
      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
      "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
      "dev": true,
      "requires": {
        "normalize-path": "^3.0.0",
        "picomatch": "^2.0.4"
      }
    },
    "arr-diff": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
      "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA="
    },
    "arr-flatten": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
      "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg=="
    },
    "arr-union": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
      "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ="
    },
    "array-each": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz",
      "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8="
    },
    "array-slice": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz",
      "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w=="
    },
    "array-unique": {
      "version": "0.3.2",
      "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
      "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg="
    },
    "array.prototype.findindex": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/array.prototype.findindex/-/array.prototype.findindex-2.1.0.tgz",
      "integrity": "sha512-25kJHCjXltdtljjwcyKvCTywmbUAeTJVB2ADVe0oP4jcefsd+K9pJJ3IdHPahLICoszcCLoNF+evWpEduzBlng==",
      "requires": {
        "define-properties": "^1.1.3",
        "es-abstract": "^1.17.4"
      }
    },
    "asn1": {
      "version": "0.2.4",
      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
      "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
      "requires": {
        "safer-buffer": "~2.1.0"
      }
    },
    "assert-plus": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
    },
    "assign-symbols": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
      "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c="
    },
    "asynckit": {
      "version": "0.4.0",
      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
    },
    "atob": {
      "version": "2.1.2",
      "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
      "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
    },
    "atomic-sleep": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
      "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="
    },
    "aws-sign2": {
      "version": "0.7.0",
      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
      "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
    },
    "aws4": {
      "version": "1.10.1",
      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz",
      "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA=="
    },
    "balanced-match": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
      "dev": true
    },
    "base": {
      "version": "0.11.2",
      "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
      "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
      "requires": {
        "cache-base": "^1.0.1",
        "class-utils": "^0.3.5",
        "component-emitter": "^1.2.1",
        "define-property": "^1.0.0",
        "isobject": "^3.0.1",
        "mixin-deep": "^1.2.0",
        "pascalcase": "^0.1.1"
      },
      "dependencies": {
        "define-property": {
          "version": "1.0.0",
          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
          "requires": {
            "is-descriptor": "^1.0.0"
          }
        },
        "is-accessor-descriptor": {
          "version": "1.0.0",
          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
          "requires": {
            "kind-of": "^6.0.0"
          }
        },
        "is-data-descriptor": {
          "version": "1.0.0",
          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
          "requires": {
            "kind-of": "^6.0.0"
          }
        },
        "is-descriptor": {
          "version": "1.0.2",
          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
          "requires": {
            "is-accessor-descriptor": "^1.0.0",
            "is-data-descriptor": "^1.0.0",
            "kind-of": "^6.0.2"
          }
        }
      }
    },
    "bcrypt-pbkdf": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
      "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
      "requires": {
        "tweetnacl": "^0.14.3"
      }
    },
    "binary-extensions": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
      "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
      "dev": true
    },
    "bl": {
      "version": "1.2.3",
      "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz",
      "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==",
      "requires": {
        "readable-stream": "^2.3.5",
        "safe-buffer": "^5.1.1"
      }
    },
    "bluebird": {
      "version": "3.7.2",
      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
      "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
    },
    "boxen": {
      "version": "4.2.0",
      "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz",
      "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==",
      "dev": true,
      "requires": {
        "ansi-align": "^3.0.0",
        "camelcase": "^5.3.1",
        "chalk": "^3.0.0",
        "cli-boxes": "^2.2.0",
        "string-width": "^4.1.0",
        "term-size": "^2.1.0",
        "type-fest": "^0.8.1",
        "widest-line": "^3.1.0"
      }
    },
    "brace-expansion": {
      "version": "1.1.11",
      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
      "dev": true,
      "requires": {
        "balanced-match": "^1.0.0",
        "concat-map": "0.0.1"
      }
    },
    "braces": {
      "version": "2.3.2",
      "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
      "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
      "requires": {
        "arr-flatten": "^1.1.0",
        "array-unique": "^0.3.2",
        "extend-shallow": "^2.0.1",
        "fill-range": "^4.0.0",
        "isobject": "^3.0.1",
        "repeat-element": "^1.1.2",
        "snapdragon": "^0.8.1",
        "snapdragon-node": "^2.0.1",
        "split-string": "^3.0.2",
        "to-regex": "^3.0.1"
      },
      "dependencies": {
        "extend-shallow": {
          "version": "2.0.1",
          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
          "requires": {
            "is-extendable": "^0.1.0"
          }
        }
      }
    },
    "buffer-writer": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
      "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="
    },
    "cache-base": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
      "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
      "requires": {
        "collection-visit": "^1.0.0",
        "component-emitter": "^1.2.1",
        "get-value": "^2.0.6",
        "has-value": "^1.0.0",
        "isobject": "^3.0.1",
        "set-value": "^2.0.0",
        "to-object-path": "^0.3.0",
        "union-value": "^1.0.0",
        "unset-value": "^1.0.0"
      }
    },
    "cacheable-request": {
      "version": "6.1.0",
      "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz",
      "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==",
      "dev": true,
      "requires": {
        "clone-response": "^1.0.2",
        "get-stream": "^5.1.0",
        "http-cache-semantics": "^4.0.0",
        "keyv": "^3.0.0",
        "lowercase-keys": "^2.0.0",
        "normalize-url": "^4.1.0",
        "responselike": "^1.0.2"
      },
      "dependencies": {
        "get-stream": {
          "version": "5.2.0",
          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
          "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
          "dev": true,
          "requires": {
            "pump": "^3.0.0"
          }
        },
        "lowercase-keys": {
          "version": "2.0.0",
          "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
          "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
          "dev": true
        },
        "pump": {
          "version": "3.0.0",
          "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
          "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
          "dev": true,
          "requires": {
            "end-of-stream": "^1.1.0",
            "once": "^1.3.1"
          }
        }
      }
    },
    "camelcase": {
      "version": "5.3.1",
      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
      "dev": true
    },
    "caseless": {
      "version": "0.12.0",
      "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
    },
    "chalk": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
      "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
      "dev": true,
      "requires": {
        "ansi-styles": "^4.1.0",
        "supports-color": "^7.1.0"
      },
      "dependencies": {
        "has-flag": {
          "version": "4.0.0",
          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
          "dev": true
        },
        "supports-color": {
          "version": "7.1.0",
          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
          "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
          "dev": true,
          "requires": {
            "has-flag": "^4.0.0"
          }
        }
      }
    },
    "chokidar": {
      "version": "3.4.2",
      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz",
      "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==",
      "dev": true,
      "requires": {
        "anymatch": "~3.1.1",
        "braces": "~3.0.2",
        "fsevents": "~2.1.2",
        "glob-parent": "~5.1.0",
        "is-binary-path": "~2.1.0",
        "is-glob": "~4.0.1",
        "normalize-path": "~3.0.0",
        "readdirp": "~3.4.0"
      },
      "dependencies": {
        "braces": {
          "version": "3.0.2",
          "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
          "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
          "dev": true,
          "requires": {
            "fill-range": "^7.0.1"
          }
        },
        "fill-range": {
          "version": "7.0.1",
          "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
          "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
          "dev": true,
          "requires": {
            "to-regex-range": "^5.0.1"
          }
        },
        "is-number": {
          "version": "7.0.0",
          "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
          "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
          "dev": true
        },
        "to-regex-range": {
          "version": "5.0.1",
          "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
          "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
          "dev": true,
          "requires": {
            "is-number": "^7.0.0"
          }
        }
      }
    },
    "ci-info": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
      "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
      "dev": true
    },
    "class-utils": {
      "version": "0.3.6",
      "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
      "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
      "requires": {
        "arr-union": "^3.1.0",
        "define-property": "^0.2.5",
        "isobject": "^3.0.0",
        "static-extend": "^0.1.1"
      },
      "dependencies": {
        "define-property": {
          "version": "0.2.5",
          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
          "requires": {
            "is-descriptor": "^0.1.0"
          }
        }
      }
    },
    "cli-boxes": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz",
      "integrity": "sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==",
      "dev": true
    },
    "clone-response": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
      "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=",
      "dev": true,
      "requires": {
        "mimic-response": "^1.0.0"
      }
    },
    "collection-visit": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
      "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
      "requires": {
        "map-visit": "^1.0.0",
        "object-visit": "^1.0.0"
      }
    },
    "color-convert": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
      "dev": true,
      "requires": {
        "color-name": "~1.1.4"
      }
    },
    "color-name": {
      "version": "1.1.4",
      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
      "dev": true
    },
    "colorette": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.1.0.tgz",
      "integrity": "sha512-6S062WDQUXi6hOfkO/sBPVwE5ASXY4G2+b4atvhJfSsuUUhIaUKlkjLe9692Ipyt5/a+IPF5aVTu3V5gvXq5cg=="
    },
    "combined-stream": {
      "version": "1.0.8",
      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
      "requires": {
        "delayed-stream": "~1.0.0"
      }
    },
    "commander": {
      "version": "4.1.1",
      "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
      "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="
    },
    "component-emitter": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
      "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
    },
    "concat-map": {
      "version": "0.0.1",
      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
      "dev": true
    },
    "configstore": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz",
      "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==",
      "dev": true,
      "requires": {
        "dot-prop": "^5.2.0",
        "graceful-fs": "^4.1.2",
        "make-dir": "^3.0.0",
        "unique-string": "^2.0.0",
        "write-file-atomic": "^3.0.0",
        "xdg-basedir": "^4.0.0"
      }
    },
    "copy-descriptor": {
      "version": "0.1.1",
      "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
      "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
    },
    "core-util-is": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
    },
    "crypto-random-string": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
      "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
      "dev": true
    },
    "dashdash": {
      "version": "1.14.1",
      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
      "requires": {
        "assert-plus": "^1.0.0"
      }
    },
    "debug": {
      "version": "4.1.1",
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
      "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
      "requires": {
        "ms": "^2.1.1"
      }
    },
    "decode-uri-component": {
      "version": "0.2.0",
      "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
      "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
    },
    "decompress-response": {
      "version": "3.3.0",
      "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
      "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
      "dev": true,
      "requires": {
        "mimic-response": "^1.0.0"
      }
    },
    "deep-extend": {
      "version": "0.6.0",
      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
      "dev": true
    },
    "defer-to-connect": {
      "version": "1.1.3",
      "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz",
      "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==",
      "dev": true
    },
    "define-properties": {
      "version": "1.1.3",
      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
      "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
      "requires": {
        "object-keys": "^1.0.12"
      }
    },
    "define-property": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
      "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
      "requires": {
        "is-descriptor": "^1.0.2",
        "isobject": "^3.0.1"
      },
      "dependencies": {
        "is-accessor-descriptor": {
          "version": "1.0.0",
          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
          "requires": {
            "kind-of": "^6.0.0"
          }
        },
        "is-data-descriptor": {
          "version": "1.0.0",
          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
          "requires": {
            "kind-of": "^6.0.0"
          }
        },
        "is-descriptor": {
          "version": "1.0.2",
          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
          "requires": {
            "is-accessor-descriptor": "^1.0.0",
            "is-data-descriptor": "^1.0.0",
            "kind-of": "^6.0.2"
          }
        }
      }
    },
    "delayed-stream": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
    },
    "depd": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
    },
    "detect-file": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
      "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc="
    },
    "dot-prop": {
      "version": "5.2.0",
      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz",
      "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==",
      "dev": true,
      "requires": {
        "is-obj": "^2.0.0"
      }
    },
    "duplexer3": {
      "version": "0.1.4",
      "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
      "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=",
      "dev": true
    },
    "ecc-jsbn": {
      "version": "0.1.2",
      "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
      "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
      "requires": {
        "jsbn": "~0.1.0",
        "safer-buffer": "^2.1.0"
      }
    },
    "emoji-regex": {
      "version": "7.0.3",
      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
      "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
      "dev": true
    },
    "end-of-stream": {
      "version": "1.4.4",
      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
      "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
      "requires": {
        "once": "^1.4.0"
      }
    },
    "es-abstract": {
      "version": "1.17.6",
      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
      "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
      "requires": {
        "es-to-primitive": "^1.2.1",
        "function-bind": "^1.1.1",
        "has": "^1.0.3",
        "has-symbols": "^1.0.1",
        "is-callable": "^1.2.0",
        "is-regex": "^1.1.0",
        "object-inspect": "^1.7.0",
        "object-keys": "^1.1.1",
        "object.assign": "^4.1.0",
        "string.prototype.trimend": "^1.0.1",
        "string.prototype.trimstart": "^1.0.1"
      }
    },
    "es-to-primitive": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
      "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
      "requires": {
        "is-callable": "^1.1.4",
        "is-date-object": "^1.0.1",
        "is-symbol": "^1.0.2"
      }
    },
    "escape-goat": {
      "version": "2.1.1",
      "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz",
      "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==",
      "dev": true
    },
    "esm": {
      "version": "3.2.25",
      "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
      "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
    },
    "eventemitter3": {
      "version": "3.1.2",
      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
      "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
    },
    "expand-brackets": {
      "version": "2.1.4",
      "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
      "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
      "requires": {
        "debug": "^2.3.3",
        "define-property": "^0.2.5",
        "extend-shallow": "^2.0.1",
        "posix-character-classes": "^0.1.0",
        "regex-not": "^1.0.0",
        "snapdragon": "^0.8.1",
        "to-regex": "^3.0.1"
      },
      "dependencies": {
        "debug": {
          "version": "2.6.9",
          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
          "requires": {
            "ms": "2.0.0"
          }
        },
        "define-property": {
          "version": "0.2.5",
          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
          "requires": {
            "is-descriptor": "^0.1.0"
          }
        },
        "extend-shallow": {
          "version": "2.0.1",
          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
          "requires": {
            "is-extendable": "^0.1.0"
          }
        },
        "ms": {
          "version": "2.0.0",
          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
        }
      }
    },
    "expand-tilde": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
      "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=",
      "requires": {
        "homedir-polyfill": "^1.0.1"
      }
    },
    "extend": {
      "version": "3.0.2",
      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
    },
    "extend-shallow": {
      "version": "3.0.2",
      "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
      "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
      "requires": {
        "assign-symbols": "^1.0.0",
        "is-extendable": "^1.0.1"
      },
      "dependencies": {
        "is-extendable": {
          "version": "1.0.1",
          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
          "requires": {
            "is-plain-object": "^2.0.4"
          }
        }
      }
    },
    "extglob": {
      "version": "2.0.4",
      "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
      "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
      "requires": {
        "array-unique": "^0.3.2",
        "define-property": "^1.0.0",
        "expand-brackets": "^2.1.4",
        "extend-shallow": "^2.0.1",
        "fragment-cache": "^0.2.1",
        "regex-not": "^1.0.0",
        "snapdragon": "^0.8.1",
        "to-regex": "^3.0.1"
      },
      "dependencies": {
        "define-property": {
          "version": "1.0.0",
          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
          "requires": {
            "is-descriptor": "^1.0.0"
          }
        },
        "extend-shallow": {
          "version": "2.0.1",
          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
          "requires": {
            "is-extendable": "^0.1.0"
          }
        },
        "is-accessor-descriptor": {
          "version": "1.0.0",
          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
          "requires": {
            "kind-of": "^6.0.0"
          }
        },
        "is-data-descriptor": {
          "version": "1.0.0",
          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
          "requires": {
            "kind-of": "^6.0.0"
          }
        },
        "is-descriptor": {
          "version": "1.0.2",
          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
          "requires": {
            "is-accessor-descriptor": "^1.0.0",
            "is-data-descriptor": "^1.0.0",
            "kind-of": "^6.0.2"
          }
        }
      }
    },
    "extsprintf": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
      "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
    },
    "fast-deep-equal": {
      "version": "3.1.3",
      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
    },
    "fast-json-stable-stringify": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
    },
    "fast-redact": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-2.0.0.tgz",
      "integrity": "sha512-zxpkULI9W9MNTK2sJ3BpPQrTEXFNESd2X6O1tXMFpK/XM0G5c5Rll2EVYZH2TqI3xRGK/VaJ+eEOt7pnENJpeA=="
    },
    "fast-safe-stringify": {
      "version": "2.0.7",
      "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz",
      "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA=="
    },
    "file-type": {
      "version": "3.9.0",
      "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
      "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek="
    },
    "fill-range": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
      "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
      "requires": {
        "extend-shallow": "^2.0.1",
        "is-number": "^3.0.0",
        "repeat-string": "^1.6.1",
        "to-regex-range": "^2.1.0"
      },
      "dependencies": {
        "extend-shallow": {
          "version": "2.0.1",
          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
          "requires": {
            "is-extendable": "^0.1.0"
          }
        }
      }
    },
    "findup-sync": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz",
      "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==",
      "requires": {
        "detect-file": "^1.0.0",
        "is-glob": "^4.0.0",
        "micromatch": "^3.0.4",
        "resolve-dir": "^1.0.1"
      }
    },
    "fined": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz",
      "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==",
      "requires": {
        "expand-tilde": "^2.0.2",
        "is-plain-object": "^2.0.3",
        "object.defaults": "^1.1.0",
        "object.pick": "^1.2.0",
        "parse-filepath": "^1.0.1"
      }
    },
    "flagged-respawn": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz",
      "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q=="
    },
    "flatstr": {
      "version": "1.0.12",
      "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz",
      "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw=="
    },
    "for-in": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
      "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA="
    },
    "for-own": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz",
      "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=",
      "requires": {
        "for-in": "^1.0.1"
      }
    },
    "forever-agent": {
      "version": "0.6.1",
      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
    },
    "form-data": {
      "version": "2.3.3",
      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
      "requires": {
        "asynckit": "^0.4.0",
        "combined-stream": "^1.0.6",
        "mime-types": "^2.1.12"
      }
    },
    "fragment-cache": {
      "version": "0.2.1",
      "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
      "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
      "requires": {
        "map-cache": "^0.2.2"
      }
    },
    "fsevents": {
      "version": "2.1.3",
      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
      "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
      "dev": true,
      "optional": true
    },
    "function-bind": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
    },
    "get-stream": {
      "version": "4.1.0",
      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
      "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
      "dev": true,
      "requires": {
        "pump": "^3.0.0"
      },
      "dependencies": {
        "pump": {
          "version": "3.0.0",
          "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
          "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
          "dev": true,
          "requires": {
            "end-of-stream": "^1.1.0",
            "once": "^1.3.1"
          }
        }
      }
    },
    "get-value": {
      "version": "2.0.6",
      "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
      "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
    },
    "getopts": {
      "version": "2.2.5",
      "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.2.5.tgz",
      "integrity": "sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA=="
    },
    "getpass": {
      "version": "0.1.7",
      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
      "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
      "requires": {
        "assert-plus": "^1.0.0"
      }
    },
    "glob-parent": {
      "version": "5.1.1",
      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
      "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
      "dev": true,
      "requires": {
        "is-glob": "^4.0.1"
      }
    },
    "global-dirs": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz",
      "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==",
      "dev": true,
      "requires": {
        "ini": "^1.3.5"
      }
    },
    "global-modules": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
      "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
      "requires": {
        "global-prefix": "^1.0.1",
        "is-windows": "^1.0.1",
        "resolve-dir": "^1.0.0"
      }
    },
    "global-prefix": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz",
      "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=",
      "requires": {
        "expand-tilde": "^2.0.2",
        "homedir-polyfill": "^1.0.1",
        "ini": "^1.3.4",
        "is-windows": "^1.0.1",
        "which": "^1.2.14"
      }
    },
    "got": {
      "version": "9.6.0",
      "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
      "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==",
      "dev": true,
      "requires": {
        "@sindresorhus/is": "^0.14.0",
        "@szmarczak/http-timer": "^1.1.2",
        "cacheable-request": "^6.0.0",
        "decompress-response": "^3.3.0",
        "duplexer3": "^0.1.4",
        "get-stream": "^4.1.0",
        "lowercase-keys": "^1.0.1",
        "mimic-response": "^1.0.1",
        "p-cancelable": "^1.0.0",
        "to-readable-stream": "^1.0.0",
        "url-parse-lax": "^3.0.0"
      }
    },
    "graceful-fs": {
      "version": "4.2.4",
      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
      "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
      "dev": true
    },
    "har-schema": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
    },
    "har-validator": {
      "version": "5.1.5",
      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
      "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
      "requires": {
        "ajv": "^6.12.3",
        "har-schema": "^2.0.0"
      }
    },
    "has": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
      "requires": {
        "function-bind": "^1.1.1"
      }
    },
    "has-flag": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
      "dev": true
    },
    "has-symbols": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
      "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg=="
    },
    "has-value": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
      "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
      "requires": {
        "get-value": "^2.0.6",
        "has-values": "^1.0.0",
        "isobject": "^3.0.0"
      }
    },
    "has-values": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
      "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
      "requires": {
        "is-number": "^3.0.0",
        "kind-of": "^4.0.0"
      },
      "dependencies": {
        "kind-of": {
          "version": "4.0.0",
          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
          "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
          "requires": {
            "is-buffer": "^1.1.5"
          }
        }
      }
    },
    "has-yarn": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz",
      "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==",
      "dev": true
    },
    "homedir-polyfill": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
      "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==",
      "requires": {
        "parse-passwd": "^1.0.0"
      }
    },
    "http-cache-semantics": {
      "version": "4.1.0",
      "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
      "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==",
      "dev": true
    },
    "http-signature": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
      "requires": {
        "assert-plus": "^1.0.0",
        "jsprim": "^1.2.2",
        "sshpk": "^1.7.0"
      }
    },
    "ignore-by-default": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
      "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
      "dev": true
    },
    "import-lazy": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
      "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=",
      "dev": true
    },
    "imurmurhash": {
      "version": "0.1.4",
      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
      "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
      "dev": true
    },
    "inherits": {
      "version": "2.0.4",
      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
    },
    "ini": {
      "version": "1.3.8",
      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
      "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
    },
    "interpret": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
      "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw=="
    },
    "is-absolute": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz",
      "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==",
      "requires": {
        "is-relative": "^1.0.0",
        "is-windows": "^1.0.1"
      }
    },
    "is-accessor-descriptor": {
      "version": "0.1.6",
      "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
      "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
      "requires": {
        "kind-of": "^3.0.2"
      },
      "dependencies": {
        "kind-of": {
          "version": "3.2.2",
          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
          "requires": {
            "is-buffer": "^1.1.5"
          }
        }
      }
    },
    "is-binary-path": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
      "dev": true,
      "requires": {
        "binary-extensions": "^2.0.0"
      }
    },
    "is-buffer": {
      "version": "1.1.6",
      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
    },
    "is-callable": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
      "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw=="
    },
    "is-ci": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
      "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
      "dev": true,
      "requires": {
        "ci-info": "^2.0.0"
      }
    },
    "is-core-module": {
      "version": "2.3.0",
      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.3.0.tgz",
      "integrity": "sha512-xSphU2KG9867tsYdLD4RWQ1VqdFl4HTO9Thf3I/3dLEfr0dbPTWKsuCKrgqMljg4nPE+Gq0VCnzT3gr0CyBmsw==",
      "requires": {
        "has": "^1.0.3"
      }
    },
    "is-data-descriptor": {
      "version": "0.1.4",
      "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
      "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
      "requires": {
        "kind-of": "^3.0.2"
      },
      "dependencies": {
        "kind-of": {
          "version": "3.2.2",
          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
          "requires": {
            "is-buffer": "^1.1.5"
          }
        }
      }
    },
    "is-date-object": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
      "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g=="
    },
    "is-descriptor": {
      "version": "0.1.6",
      "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
      "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
      "requires": {
        "is-accessor-descriptor": "^0.1.6",
        "is-data-descriptor": "^0.1.4",
        "kind-of": "^5.0.0"
      },
      "dependencies": {
        "kind-of": {
          "version": "5.1.0",
          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
          "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw=="
        }
      }
    },
    "is-extendable": {
      "version": "0.1.1",
      "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
      "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
    },
    "is-extglob": {
      "version": "2.1.1",
      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
    },
    "is-fullwidth-code-point": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
      "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
      "dev": true
    },
    "is-glob": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
      "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
      "requires": {
        "is-extglob": "^2.1.1"
      }
    },
    "is-installed-globally": {
      "version": "0.3.2",
      "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz",
      "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==",
      "dev": true,
      "requires": {
        "global-dirs": "^2.0.1",
        "is-path-inside": "^3.0.1"
      }
    },
    "is-npm": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz",
      "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==",
      "dev": true
    },
    "is-number": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
      "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
      "requires": {
        "kind-of": "^3.0.2"
      },
      "dependencies": {
        "kind-of": {
          "version": "3.2.2",
          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
          "requires": {
            "is-buffer": "^1.1.5"
          }
        }
      }
    },
    "is-obj": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
      "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
      "dev": true
    },
    "is-path-inside": {
      "version": "3.0.2",
      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz",
      "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==",
      "dev": true
    },
    "is-plain-object": {
      "version": "2.0.4",
      "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
      "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
      "requires": {
        "isobject": "^3.0.1"
      }
    },
    "is-regex": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
      "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
      "requires": {
        "has-symbols": "^1.0.1"
      }
    },
    "is-relative": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
      "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==",
      "requires": {
        "is-unc-path": "^1.0.0"
      }
    },
    "is-symbol": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
      "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
      "requires": {
        "has-symbols": "^1.0.1"
      }
    },
    "is-typedarray": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
    },
    "is-unc-path": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
      "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==",
      "requires": {
        "unc-path-regex": "^0.1.2"
      }
    },
    "is-windows": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
      "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="
    },
    "is-yarn-global": {
      "version": "0.3.0",
      "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz",
      "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==",
      "dev": true
    },
    "isarray": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
    },
    "isexe": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
    },
    "isobject": {
      "version": "3.0.1",
      "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
      "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
    },
    "isstream": {
      "version": "0.1.2",
      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
      "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
    },
    "jsbn": {
      "version": "0.1.1",
      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
    },
    "json-buffer": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
      "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=",
      "dev": true
    },
    "json-schema": {
      "version": "0.2.3",
      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
    },
    "json-schema-traverse": {
      "version": "0.4.1",
      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
    },
    "json-stringify-safe": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
    },
    "jsprim": {
      "version": "1.4.1",
      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
      "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
      "requires": {
        "assert-plus": "1.0.0",
        "extsprintf": "1.3.0",
        "json-schema": "0.2.3",
        "verror": "1.10.0"
      }
    },
    "keyv": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
      "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==",
      "dev": true,
      "requires": {
        "json-buffer": "3.0.0"
      }
    },
    "kind-of": {
      "version": "6.0.3",
      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
      "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="
    },
    "knex": {
      "version": "0.20.15",
      "resolved": "https://registry.npmjs.org/knex/-/knex-0.20.15.tgz",
      "integrity": "sha512-WHmvgfQfxA5v8pyb9zbskxCS1L1WmYgUbwBhHojlkmdouUOazvroUWlCr6KIKMQ8anXZh1NXOOtIUMnxENZG5Q==",
      "requires": {
        "colorette": "1.1.0",
        "commander": "^4.1.1",
        "debug": "4.1.1",
        "esm": "^3.2.25",
        "getopts": "2.2.5",
        "inherits": "~2.0.4",
        "interpret": "^2.0.0",
        "liftoff": "3.1.0",
        "lodash": "^4.17.15",
        "mkdirp": "^0.5.1",
        "pg-connection-string": "2.1.0",
        "tarn": "^2.0.0",
        "tildify": "2.0.0",
        "uuid": "^7.0.1",
        "v8flags": "^3.1.3"
      },
      "dependencies": {
        "uuid": {
          "version": "7.0.3",
          "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz",
          "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="
        }
      }
    },
    "latest-version": {
      "version": "5.1.0",
      "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz",
      "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==",
      "dev": true,
      "requires": {
        "package-json": "^6.3.0"
      }
    },
    "liftoff": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz",
      "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==",
      "requires": {
        "extend": "^3.0.0",
        "findup-sync": "^3.0.0",
        "fined": "^1.0.1",
        "flagged-respawn": "^1.0.0",
        "is-plain-object": "^2.0.4",
        "object.map": "^1.0.0",
        "rechoir": "^0.6.2",
        "resolve": "^1.1.7"
      }
    },
    "lodash": {
      "version": "4.17.21",
      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
    },
    "lowercase-keys": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
      "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
      "dev": true
    },
    "make-dir": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
      "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
      "dev": true,
      "requires": {
        "semver": "^6.0.0"
      },
      "dependencies": {
        "semver": {
          "version": "6.3.0",
          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
          "dev": true
        }
      }
    },
    "make-iterator": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz",
      "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==",
      "requires": {
        "kind-of": "^6.0.2"
      }
    },
    "map-cache": {
      "version": "0.2.2",
      "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
      "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8="
    },
    "map-visit": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
      "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
      "requires": {
        "object-visit": "^1.0.0"
      }
    },
    "micromatch": {
      "version": "3.1.10",
      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
      "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
      "requires": {
        "arr-diff": "^4.0.0",
        "array-unique": "^0.3.2",
        "braces": "^2.3.1",
        "define-property": "^2.0.2",
        "extend-shallow": "^3.0.2",
        "extglob": "^2.0.4",
        "fragment-cache": "^0.2.1",
        "kind-of": "^6.0.2",
        "nanomatch": "^1.2.9",
        "object.pick": "^1.3.0",
        "regex-not": "^1.0.0",
        "snapdragon": "^0.8.1",
        "to-regex": "^3.0.2"
      }
    },
    "mime": {
      "version": "1.6.0",
      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
    },
    "mime-db": {
      "version": "1.43.0",
      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
      "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ=="
    },
    "mime-types": {
      "version": "2.1.26",
      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
      "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
      "requires": {
        "mime-db": "1.43.0"
      }
    },
    "mimic-response": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
      "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
      "dev": true
    },
    "minimatch": {
      "version": "3.0.4",
      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
      "dev": true,
      "requires": {
        "brace-expansion": "^1.1.7"
      }
    },
    "minimist": {
      "version": "1.2.5",
      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
    },
    "mixin-deep": {
      "version": "1.3.2",
      "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
      "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
      "requires": {
        "for-in": "^1.0.2",
        "is-extendable": "^1.0.1"
      },
      "dependencies": {
        "is-extendable": {
          "version": "1.0.1",
          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
          "requires": {
            "is-plain-object": "^2.0.4"
          }
        }
      }
    },
    "mkdirp": {
      "version": "0.5.5",
      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
      "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
      "requires": {
        "minimist": "^1.2.5"
      }
    },
    "ms": {
      "version": "2.1.2",
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
    },
    "nanomatch": {
      "version": "1.2.13",
      "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
      "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
      "requires": {
        "arr-diff": "^4.0.0",
        "array-unique": "^0.3.2",
        "define-property": "^2.0.2",
        "extend-shallow": "^3.0.2",
        "fragment-cache": "^0.2.1",
        "is-windows": "^1.0.2",
        "kind-of": "^6.0.2",
        "object.pick": "^1.3.0",
        "regex-not": "^1.0.0",
        "snapdragon": "^0.8.1",
        "to-regex": "^3.0.1"
      }
    },
    "node-telegram-bot-api": {
      "version": "0.40.0",
      "resolved": "https://registry.npmjs.org/node-telegram-bot-api/-/node-telegram-bot-api-0.40.0.tgz",
      "integrity": "sha512-kDVCU1Y0L7hDnkm8oosO7cKIRyftPOvIGMvDbj7CU/FDIqqkC13VytRieHb/pFgTfFmiCpBTzAeK66YLHIfchQ==",
      "requires": {
        "array.prototype.findindex": "^2.0.2",
        "bl": "^1.2.1",
        "bluebird": "^3.5.1",
        "debug": "^3.1.0",
        "depd": "^1.1.1",
        "eventemitter3": "^3.0.0",
        "file-type": "^3.9.0",
        "mime": "^1.6.0",
        "pump": "^2.0.0",
        "request": "^2.83.0",
        "request-promise": "^4.2.2"
      },
      "dependencies": {
        "debug": {
          "version": "3.2.6",
          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
          "requires": {
            "ms": "^2.1.1"
          }
        }
      }
    },
    "nodemon": {
      "version": "2.0.4",
      "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz",
      "integrity": "sha512-Ltced+hIfTmaS28Zjv1BM552oQ3dbwPqI4+zI0SLgq+wpJhSyqgYude/aZa/3i31VCQWMfXJVxvu86abcam3uQ==",
      "dev": true,
      "requires": {
        "chokidar": "^3.2.2",
        "debug": "^3.2.6",
        "ignore-by-default": "^1.0.1",
        "minimatch": "^3.0.4",
        "pstree.remy": "^1.1.7",
        "semver": "^5.7.1",
        "supports-color": "^5.5.0",
        "touch": "^3.1.0",
        "undefsafe": "^2.0.2",
        "update-notifier": "^4.0.0"
      },
      "dependencies": {
        "debug": {
          "version": "3.2.6",
          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
          "dev": true,
          "requires": {
            "ms": "^2.1.1"
          }
        }
      }
    },
    "nopt": {
      "version": "1.0.10",
      "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
      "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=",
      "dev": true,
      "requires": {
        "abbrev": "1"
      }
    },
    "normalize-path": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
      "dev": true
    },
    "normalize-url": {
      "version": "4.5.0",
      "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz",
      "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==",
      "dev": true
    },
    "oauth-sign": {
      "version": "0.9.0",
      "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
      "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
    },
    "object-copy": {
      "version": "0.1.0",
      "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
      "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
      "requires": {
        "copy-descriptor": "^0.1.0",
        "define-property": "^0.2.5",
        "kind-of": "^3.0.3"
      },
      "dependencies": {
        "define-property": {
          "version": "0.2.5",
          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
          "requires": {
            "is-descriptor": "^0.1.0"
          }
        },
        "kind-of": {
          "version": "3.2.2",
          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
          "requires": {
            "is-buffer": "^1.1.5"
          }
        }
      }
    },
    "object-inspect": {
      "version": "1.8.0",
      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz",
      "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA=="
    },
    "object-keys": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
    },
    "object-visit": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
      "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
      "requires": {
        "isobject": "^3.0.0"
      }
    },
    "object.assign": {
      "version": "4.1.0",
      "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
      "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
      "requires": {
        "define-properties": "^1.1.2",
        "function-bind": "^1.1.1",
        "has-symbols": "^1.0.0",
        "object-keys": "^1.0.11"
      }
    },
    "object.defaults": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz",
      "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=",
      "requires": {
        "array-each": "^1.0.1",
        "array-slice": "^1.0.0",
        "for-own": "^1.0.0",
        "isobject": "^3.0.0"
      }
    },
    "object.map": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz",
      "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=",
      "requires": {
        "for-own": "^1.0.0",
        "make-iterator": "^1.0.0"
      }
    },
    "object.pick": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
      "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
      "requires": {
        "isobject": "^3.0.1"
      }
    },
    "once": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
      "requires": {
        "wrappy": "1"
      }
    },
    "p-cancelable": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz",
      "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==",
      "dev": true
    },
    "package-json": {
      "version": "6.5.0",
      "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz",
      "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==",
      "dev": true,
      "requires": {
        "got": "^9.6.0",
        "registry-auth-token": "^4.0.0",
        "registry-url": "^5.0.0",
        "semver": "^6.2.0"
      },
      "dependencies": {
        "semver": {
          "version": "6.3.0",
          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
          "dev": true
        }
      }
    },
    "packet-reader": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
      "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
    },
    "parse-filepath": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz",
      "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=",
      "requires": {
        "is-absolute": "^1.0.0",
        "map-cache": "^0.2.0",
        "path-root": "^0.1.1"
      }
    },
    "parse-passwd": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
      "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY="
    },
    "pascalcase": {
      "version": "0.1.1",
      "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
      "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ="
    },
    "path-parse": {
      "version": "1.0.6",
      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
    },
    "path-root": {
      "version": "0.1.1",
      "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz",
      "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=",
      "requires": {
        "path-root-regex": "^0.1.0"
      }
    },
    "path-root-regex": {
      "version": "0.1.2",
      "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz",
      "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0="
    },
    "performance-now": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
    },
    "pg": {
      "version": "8.6.0",
      "resolved": "https://registry.npmjs.org/pg/-/pg-8.6.0.tgz",
      "integrity": "sha512-qNS9u61lqljTDFvmk/N66EeGq3n6Ujzj0FFyNMGQr6XuEv4tgNTXvJQTfJdcvGit5p5/DWPu+wj920hAJFI+QQ==",
      "requires": {
        "buffer-writer": "2.0.0",
        "packet-reader": "1.0.0",
        "pg-connection-string": "^2.5.0",
        "pg-pool": "^3.3.0",
        "pg-protocol": "^1.5.0",
        "pg-types": "^2.1.0",
        "pgpass": "1.x"
      },
      "dependencies": {
        "pg-connection-string": {
          "version": "2.5.0",
          "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
          "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
        }
      }
    },
    "pg-connection-string": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.1.0.tgz",
      "integrity": "sha512-bhlV7Eq09JrRIvo1eKngpwuqKtJnNhZdpdOlvrPrA4dxqXPjxSrbNrfnIDmTpwMyRszrcV4kU5ZA4mMsQUrjdg=="
    },
    "pg-int8": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
      "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
    },
    "pg-pool": {
      "version": "3.3.0",
      "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.3.0.tgz",
      "integrity": "sha512-0O5huCql8/D6PIRFAlmccjphLYWC+JIzvUhSzXSpGaf+tjTZc4nn+Lr7mLXBbFJfvwbP0ywDv73EiaBsxn7zdg=="
    },
    "pg-protocol": {
      "version": "1.5.0",
      "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
      "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ=="
    },
    "pg-types": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
      "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
      "requires": {
        "pg-int8": "1.0.1",
        "postgres-array": "~2.0.0",
        "postgres-bytea": "~1.0.0",
        "postgres-date": "~1.0.4",
        "postgres-interval": "^1.1.0"
      }
    },
    "pgpass": {
      "version": "1.0.4",
      "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.4.tgz",
      "integrity": "sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==",
      "requires": {
        "split2": "^3.1.1"
      }
    },
    "picomatch": {
      "version": "2.2.2",
      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
      "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
      "dev": true
    },
    "pino": {
      "version": "6.5.1",
      "resolved": "https://registry.npmjs.org/pino/-/pino-6.5.1.tgz",
      "integrity": "sha512-76+RUhQkqjUD4AtQcSfEzh6vlsjXmoWZK5gg+2d70aCLXZTbo4/5js4I9rN1Xk6z1h2/7pnOFX10G4c2T4qNiA==",
      "requires": {
        "fast-redact": "^2.0.0",
        "fast-safe-stringify": "^2.0.7",
        "flatstr": "^1.0.12",
        "pino-std-serializers": "^2.4.2",
        "quick-format-unescaped": "^4.0.1",
        "sonic-boom": "^1.0.2"
      }
    },
    "pino-std-serializers": {
      "version": "2.5.0",
      "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-2.5.0.tgz",
      "integrity": "sha512-wXqbqSrIhE58TdrxxlfLwU9eDhrzppQDvGhBEr1gYbzzM4KKo3Y63gSjiDXRKLVS2UOXdPNR2v+KnQgNrs+xUg=="
    },
    "posix-character-classes": {
      "version": "0.1.1",
      "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
      "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
    },
    "postgres-array": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
      "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="
    },
    "postgres-bytea": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
      "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU="
    },
    "postgres-date": {
      "version": "1.0.7",
      "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
      "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="
    },
    "postgres-interval": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
      "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
      "requires": {
        "xtend": "^4.0.0"
      }
    },
    "prepend-http": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
      "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=",
      "dev": true
    },
    "process-nextick-args": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
    },
    "psl": {
      "version": "1.8.0",
      "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
      "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
    },
    "pstree.remy": {
      "version": "1.1.8",
      "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
      "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
      "dev": true
    },
    "pump": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
      "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
      "requires": {
        "end-of-stream": "^1.1.0",
        "once": "^1.3.1"
      }
    },
    "punycode": {
      "version": "2.1.1",
      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
    },
    "pupa": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz",
      "integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==",
      "dev": true,
      "requires": {
        "escape-goat": "^2.0.0"
      }
    },
    "qs": {
      "version": "6.5.2",
      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
      "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
    },
    "quick-format-unescaped": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.1.tgz",
      "integrity": "sha512-RyYpQ6Q5/drsJyOhrWHYMWTedvjTIat+FTwv0K4yoUxzvekw2aRHMQJLlnvt8UantkZg2++bEzD9EdxXqkWf4A=="
    },
    "rc": {
      "version": "1.2.8",
      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
      "dev": true,
      "requires": {
        "deep-extend": "^0.6.0",
        "ini": "~1.3.0",
        "minimist": "^1.2.0",
        "strip-json-comments": "~2.0.1"
      }
    },
    "readable-stream": {
      "version": "2.3.7",
      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
      "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
      "requires": {
        "core-util-is": "~1.0.0",
        "inherits": "~2.0.3",
        "isarray": "~1.0.0",
        "process-nextick-args": "~2.0.0",
        "safe-buffer": "~5.1.1",
        "string_decoder": "~1.1.1",
        "util-deprecate": "~1.0.1"
      }
    },
    "readdirp": {
      "version": "3.4.0",
      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
      "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==",
      "dev": true,
      "requires": {
        "picomatch": "^2.2.1"
      }
    },
    "rechoir": {
      "version": "0.6.2",
      "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
      "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
      "requires": {
        "resolve": "^1.1.6"
      }
    },
    "regex-not": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
      "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
      "requires": {
        "extend-shallow": "^3.0.2",
        "safe-regex": "^1.1.0"
      }
    },
    "registry-auth-token": {
      "version": "4.2.0",
      "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.0.tgz",
      "integrity": "sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w==",
      "dev": true,
      "requires": {
        "rc": "^1.2.8"
      }
    },
    "registry-url": {
      "version": "5.1.0",
      "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz",
      "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==",
      "dev": true,
      "requires": {
        "rc": "^1.2.8"
      }
    },
    "repeat-element": {
      "version": "1.1.4",
      "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz",
      "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ=="
    },
    "repeat-string": {
      "version": "1.6.1",
      "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
      "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
    },
    "request": {
      "version": "2.88.2",
      "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
      "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
      "requires": {
        "aws-sign2": "~0.7.0",
        "aws4": "^1.8.0",
        "caseless": "~0.12.0",
        "combined-stream": "~1.0.6",
        "extend": "~3.0.2",
        "forever-agent": "~0.6.1",
        "form-data": "~2.3.2",
        "har-validator": "~5.1.3",
        "http-signature": "~1.2.0",
        "is-typedarray": "~1.0.0",
        "isstream": "~0.1.2",
        "json-stringify-safe": "~5.0.1",
        "mime-types": "~2.1.19",
        "oauth-sign": "~0.9.0",
        "performance-now": "^2.1.0",
        "qs": "~6.5.2",
        "safe-buffer": "^5.1.2",
        "tough-cookie": "~2.5.0",
        "tunnel-agent": "^0.6.0",
        "uuid": "^3.3.2"
      },
      "dependencies": {
        "uuid": {
          "version": "3.4.0",
          "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
          "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
        }
      }
    },
    "request-promise": {
      "version": "4.2.6",
      "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz",
      "integrity": "sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==",
      "requires": {
        "bluebird": "^3.5.0",
        "request-promise-core": "1.1.4",
        "stealthy-require": "^1.1.1",
        "tough-cookie": "^2.3.3"
      }
    },
    "request-promise-core": {
      "version": "1.1.4",
      "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz",
      "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
      "requires": {
        "lodash": "^4.17.19"
      },
      "dependencies": {
        "lodash": {
          "version": "4.17.20",
          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
          "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
        }
      }
    },
    "resolve": {
      "version": "1.20.0",
      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
      "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
      "requires": {
        "is-core-module": "^2.2.0",
        "path-parse": "^1.0.6"
      }
    },
    "resolve-dir": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz",
      "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=",
      "requires": {
        "expand-tilde": "^2.0.0",
        "global-modules": "^1.0.0"
      }
    },
    "resolve-url": {
      "version": "0.2.1",
      "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
      "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
    },
    "responselike": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz",
      "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=",
      "dev": true,
      "requires": {
        "lowercase-keys": "^1.0.0"
      }
    },
    "ret": {
      "version": "0.1.15",
      "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
      "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
    },
    "safe-buffer": {
      "version": "5.1.2",
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
    },
    "safe-regex": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
      "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
      "requires": {
        "ret": "~0.1.10"
      }
    },
    "safer-buffer": {
      "version": "2.1.2",
      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
    },
    "semver": {
      "version": "5.7.1",
      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
      "dev": true
    },
    "semver-diff": {
      "version": "3.1.1",
      "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz",
      "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==",
      "dev": true,
      "requires": {
        "semver": "^6.3.0"
      },
      "dependencies": {
        "semver": {
          "version": "6.3.0",
          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
          "dev": true
        }
      }
    },
    "set-value": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
      "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
      "requires": {
        "extend-shallow": "^2.0.1",
        "is-extendable": "^0.1.1",
        "is-plain-object": "^2.0.3",
        "split-string": "^3.0.1"
      },
      "dependencies": {
        "extend-shallow": {
          "version": "2.0.1",
          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
          "requires": {
            "is-extendable": "^0.1.0"
          }
        }
      }
    },
    "signal-exit": {
      "version": "3.0.3",
      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
      "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
      "dev": true
    },
    "snapdragon": {
      "version": "0.8.2",
      "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
      "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
      "requires": {
        "base": "^0.11.1",
        "debug": "^2.2.0",
        "define-property": "^0.2.5",
        "extend-shallow": "^2.0.1",
        "map-cache": "^0.2.2",
        "source-map": "^0.5.6",
        "source-map-resolve": "^0.5.0",
        "use": "^3.1.0"
      },
      "dependencies": {
        "debug": {
          "version": "2.6.9",
          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
          "requires": {
            "ms": "2.0.0"
          }
        },
        "define-property": {
          "version": "0.2.5",
          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
          "requires": {
            "is-descriptor": "^0.1.0"
          }
        },
        "extend-shallow": {
          "version": "2.0.1",
          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
          "requires": {
            "is-extendable": "^0.1.0"
          }
        },
        "ms": {
          "version": "2.0.0",
          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
        }
      }
    },
    "snapdragon-node": {
      "version": "2.1.1",
      "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
      "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
      "requires": {
        "define-property": "^1.0.0",
        "isobject": "^3.0.0",
        "snapdragon-util": "^3.0.1"
      },
      "dependencies": {
        "define-property": {
          "version": "1.0.0",
          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
          "requires": {
            "is-descriptor": "^1.0.0"
          }
        },
        "is-accessor-descriptor": {
          "version": "1.0.0",
          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
          "requires": {
            "kind-of": "^6.0.0"
          }
        },
        "is-data-descriptor": {
          "version": "1.0.0",
          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
          "requires": {
            "kind-of": "^6.0.0"
          }
        },
        "is-descriptor": {
          "version": "1.0.2",
          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
          "requires": {
            "is-accessor-descriptor": "^1.0.0",
            "is-data-descriptor": "^1.0.0",
            "kind-of": "^6.0.2"
          }
        }
      }
    },
    "snapdragon-util": {
      "version": "3.0.1",
      "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
      "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
      "requires": {
        "kind-of": "^3.2.0"
      },
      "dependencies": {
        "kind-of": {
          "version": "3.2.2",
          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
          "requires": {
            "is-buffer": "^1.1.5"
          }
        }
      }
    },
    "sonic-boom": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.1.0.tgz",
      "integrity": "sha512-JyOf+Xt7GBN4tAic/DD1Bitw6OMgSHAnswhPeOiLpfRoSjPNjEIi73UF3OxHzhSNn9WavxGuCZzprFCGFSNwog==",
      "requires": {
        "atomic-sleep": "^1.0.0",
        "flatstr": "^1.0.12"
      }
    },
    "source-map": {
      "version": "0.5.7",
      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
      "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
    },
    "source-map-resolve": {
      "version": "0.5.3",
      "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
      "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
      "requires": {
        "atob": "^2.1.2",
        "decode-uri-component": "^0.2.0",
        "resolve-url": "^0.2.1",
        "source-map-url": "^0.4.0",
        "urix": "^0.1.0"
      }
    },
    "source-map-url": {
      "version": "0.4.1",
      "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
      "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw=="
    },
    "split-string": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
      "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
      "requires": {
        "extend-shallow": "^3.0.0"
      }
    },
    "split2": {
      "version": "3.2.2",
      "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz",
      "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==",
      "requires": {
        "readable-stream": "^3.0.0"
      },
      "dependencies": {
        "readable-stream": {
          "version": "3.6.0",
          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
          "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
          "requires": {
            "inherits": "^2.0.3",
            "string_decoder": "^1.1.1",
            "util-deprecate": "^1.0.1"
          }
        }
      }
    },
    "sshpk": {
      "version": "1.16.1",
      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
      "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
      "requires": {
        "asn1": "~0.2.3",
        "assert-plus": "^1.0.0",
        "bcrypt-pbkdf": "^1.0.0",
        "dashdash": "^1.12.0",
        "ecc-jsbn": "~0.1.1",
        "getpass": "^0.1.1",
        "jsbn": "~0.1.0",
        "safer-buffer": "^2.0.2",
        "tweetnacl": "~0.14.0"
      }
    },
    "static-extend": {
      "version": "0.1.2",
      "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
      "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
      "requires": {
        "define-property": "^0.2.5",
        "object-copy": "^0.1.0"
      },
      "dependencies": {
        "define-property": {
          "version": "0.2.5",
          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
          "requires": {
            "is-descriptor": "^0.1.0"
          }
        }
      }
    },
    "stealthy-require": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
      "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
    },
    "string-width": {
      "version": "4.2.0",
      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
      "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
      "dev": true,
      "requires": {
        "emoji-regex": "^8.0.0",
        "is-fullwidth-code-point": "^3.0.0",
        "strip-ansi": "^6.0.0"
      },
      "dependencies": {
        "ansi-regex": {
          "version": "5.0.0",
          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
          "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
          "dev": true
        },
        "emoji-regex": {
          "version": "8.0.0",
          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
          "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
          "dev": true
        },
        "is-fullwidth-code-point": {
          "version": "3.0.0",
          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
          "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
          "dev": true
        },
        "strip-ansi": {
          "version": "6.0.0",
          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
          "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
          "dev": true,
          "requires": {
            "ansi-regex": "^5.0.0"
          }
        }
      }
    },
    "string.prototype.trimend": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz",
      "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==",
      "requires": {
        "define-properties": "^1.1.3",
        "es-abstract": "^1.17.5"
      }
    },
    "string.prototype.trimstart": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz",
      "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==",
      "requires": {
        "define-properties": "^1.1.3",
        "es-abstract": "^1.17.5"
      }
    },
    "string_decoder": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
      "requires": {
        "safe-buffer": "~5.1.0"
      }
    },
    "strip-ansi": {
      "version": "5.2.0",
      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
      "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
      "dev": true,
      "requires": {
        "ansi-regex": "^4.1.0"
      }
    },
    "strip-json-comments": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
      "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
      "dev": true
    },
    "supports-color": {
      "version": "5.5.0",
      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
      "dev": true,
      "requires": {
        "has-flag": "^3.0.0"
      }
    },
    "tarn": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/tarn/-/tarn-2.0.0.tgz",
      "integrity": "sha512-7rNMCZd3s9bhQh47ksAQd92ADFcJUjjbyOvyFjNLwTPpGieFHMC84S+LOzw0fx1uh6hnDz/19r8CPMnIjJlMMA=="
    },
    "term-size": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz",
      "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==",
      "dev": true
    },
    "tildify": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz",
      "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw=="
    },
    "to-object-path": {
      "version": "0.3.0",
      "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
      "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
      "requires": {
        "kind-of": "^3.0.2"
      },
      "dependencies": {
        "kind-of": {
          "version": "3.2.2",
          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
          "requires": {
            "is-buffer": "^1.1.5"
          }
        }
      }
    },
    "to-readable-stream": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz",
      "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==",
      "dev": true
    },
    "to-regex": {
      "version": "3.0.2",
      "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
      "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
      "requires": {
        "define-property": "^2.0.2",
        "extend-shallow": "^3.0.2",
        "regex-not": "^1.0.2",
        "safe-regex": "^1.1.0"
      }
    },
    "to-regex-range": {
      "version": "2.1.1",
      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
      "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
      "requires": {
        "is-number": "^3.0.0",
        "repeat-string": "^1.6.1"
      }
    },
    "touch": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
      "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
      "dev": true,
      "requires": {
        "nopt": "~1.0.10"
      }
    },
    "tough-cookie": {
      "version": "2.5.0",
      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
      "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
      "requires": {
        "psl": "^1.1.28",
        "punycode": "^2.1.1"
      }
    },
    "tunnel-agent": {
      "version": "0.6.0",
      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
      "requires": {
        "safe-buffer": "^5.0.1"
      }
    },
    "tweetnacl": {
      "version": "0.14.5",
      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
      "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
    },
    "type-fest": {
      "version": "0.8.1",
      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
      "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
      "dev": true
    },
    "typedarray-to-buffer": {
      "version": "3.1.5",
      "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
      "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
      "dev": true,
      "requires": {
        "is-typedarray": "^1.0.0"
      }
    },
    "unc-path-regex": {
      "version": "0.1.2",
      "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
      "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo="
    },
    "undefsafe": {
      "version": "2.0.3",
      "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz",
      "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==",
      "dev": true,
      "requires": {
        "debug": "^2.2.0"
      },
      "dependencies": {
        "debug": {
          "version": "2.6.9",
          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
          "dev": true,
          "requires": {
            "ms": "2.0.0"
          }
        },
        "ms": {
          "version": "2.0.0",
          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
          "dev": true
        }
      }
    },
    "union-value": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
      "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
      "requires": {
        "arr-union": "^3.1.0",
        "get-value": "^2.0.6",
        "is-extendable": "^0.1.1",
        "set-value": "^2.0.1"
      }
    },
    "unique-string": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",
      "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==",
      "dev": true,
      "requires": {
        "crypto-random-string": "^2.0.0"
      }
    },
    "unset-value": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
      "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
      "requires": {
        "has-value": "^0.3.1",
        "isobject": "^3.0.0"
      },
      "dependencies": {
        "has-value": {
          "version": "0.3.1",
          "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
          "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
          "requires": {
            "get-value": "^2.0.3",
            "has-values": "^0.1.4",
            "isobject": "^2.0.0"
          },
          "dependencies": {
            "isobject": {
              "version": "2.1.0",
              "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
              "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
              "requires": {
                "isarray": "1.0.0"
              }
            }
          }
        },
        "has-values": {
          "version": "0.1.4",
          "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
          "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E="
        }
      }
    },
    "update-notifier": {
      "version": "4.1.1",
      "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.1.tgz",
      "integrity": "sha512-9y+Kds0+LoLG6yN802wVXoIfxYEwh3FlZwzMwpCZp62S2i1/Jzeqb9Eeeju3NSHccGGasfGlK5/vEHbAifYRDg==",
      "dev": true,
      "requires": {
        "boxen": "^4.2.0",
        "chalk": "^3.0.0",
        "configstore": "^5.0.1",
        "has-yarn": "^2.1.0",
        "import-lazy": "^2.1.0",
        "is-ci": "^2.0.0",
        "is-installed-globally": "^0.3.1",
        "is-npm": "^4.0.0",
        "is-yarn-global": "^0.3.0",
        "latest-version": "^5.0.0",
        "pupa": "^2.0.1",
        "semver-diff": "^3.1.1",
        "xdg-basedir": "^4.0.0"
      }
    },
    "uri-js": {
      "version": "4.2.2",
      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
      "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
      "requires": {
        "punycode": "^2.1.0"
      }
    },
    "urix": {
      "version": "0.1.0",
      "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
      "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI="
    },
    "url-parse-lax": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
      "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=",
      "dev": true,
      "requires": {
        "prepend-http": "^2.0.0"
      }
    },
    "use": {
      "version": "3.1.1",
      "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
      "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
    },
    "util-deprecate": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
    },
    "uuid": {
      "version": "8.3.0",
      "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz",
      "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ=="
    },
    "v8flags": {
      "version": "3.2.0",
      "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz",
      "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==",
      "requires": {
        "homedir-polyfill": "^1.0.1"
      }
    },
    "verror": {
      "version": "1.10.0",
      "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
      "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
      "requires": {
        "assert-plus": "^1.0.0",
        "core-util-is": "1.0.2",
        "extsprintf": "^1.2.0"
      }
    },
    "which": {
      "version": "1.3.1",
      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
      "requires": {
        "isexe": "^2.0.0"
      }
    },
    "widest-line": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz",
      "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==",
      "dev": true,
      "requires": {
        "string-width": "^4.0.0"
      }
    },
    "wrappy": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
    },
    "write-file-atomic": {
      "version": "3.0.3",
      "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
      "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
      "dev": true,
      "requires": {
        "imurmurhash": "^0.1.4",
        "is-typedarray": "^1.0.0",
        "signal-exit": "^3.0.2",
        "typedarray-to-buffer": "^3.1.5"
      }
    },
    "xdg-basedir": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz",
      "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==",
      "dev": true
    },
    "xtend": {
      "version": "4.0.2",
      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
    }
  }
}

↪️package.json

{
  "name": "telegram-forwarder-bot-agent",
  "version": "1.0.0",
  "description": "Telegram bot that acts as a frontend for all the controls",
  "main": "app.js",
  "scripts": {
    "start": "node bot.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/adityathebe/telegramForwarder.git"
  },
  "keywords": [],
  "author": "Aditya Thebe <contact@adityathebe.com> (https://www.adityathebe.com)",
  "license": "ISC",
  "dependencies": {
    "knex": "^0.20.15",
    "node-telegram-bot-api": "^0.40.0",
    "pg": "^8.6.0",
    "pino": "^6.5.1",
    "request": "^2.88.2",
    "uuid": "^8.3.0"
  },
  "devDependencies": {
    "nodemon": "^2.0.4"
  }
}

↪️readme.md

Database

1. User

  • chat_id* (string)
  • username (string)
  • ref_code (string)
  • ref_by (string)
  • premium (Boolean)

2. Redirections

  • id* (int)
  • owner (string)
  • source (string)
  • source_title (string)
  • destination (string)
  • destination_title (string)
  • active (boolean);

3. Filters

  • id* (int)
  • red_id
  • name
  • state

Features

Free

  1. /add -- Adding redirections allows you to setup a automatic routing of messages from any telegram channels to your own telegram channels 10 active redirections
  2. /activate -- Activate a redirection from your list. Bot does automatic forwarding of messages only for active redirections
  3. /deactivate -- deactivates a redirection from your list. Bot does not do automatic forwarding of messages for inactive re-directions
  4. /list -- get list of redirections with their corresponding redirection IDs
  5. /remove -- remove the redirection with id REDIRECTION_ID from your redirections list
  6. /filters -- get information about filters, applied to redirection with id REDIRECTION_ID
  7. /filter -- use filters allow you to make the bot skip messages that you do not want to be forwarded. Message can also be forwarded, but filter removes photos, documents, animations, stickers, links, hashtags, etc. Please see MultiFeed Bot for all filters it has that I want.
    • FILTER_NAME = name of the filter
    • REDIRECTION_ID = ID of the redirection for which you update the filter’s state
    • STATE = state of filter; use on for enabling filter and off for disabling it.
  8. /me – gets information about your account (including information about activated paid bot features & invited users). List all PAID features with subscription end date. PAID features may have a different subscription end date, depending on date customer purchased it. Admin has the ability to enable/disable PAID features & change subscription end date for each feature.
  9. /ref – Share referral link with others and get bonus. +30 day subscription to all PAID features when referral pays for subscription. Assign unique referral link/ID for each subscriber. Admin will know which existing customer had referred the new subscriber. Admin will manually update subscription of subscriber’s account.

Premium

  1. Use copy-paste mode of forwarding Purpose: Remove "Forwarded from" header (aka "link to the sourcing channel") from redirected messages
  2. Connect Telegram account to Bot Purpose: Setup redirections from channels you don't have an invitation link for, but are a member of, which is the per-requisite for setting up redirections from/to bots, groups, people and transformation feature.
  3. Enable Transformation Purpose: Replace particular words in messages with your own words. Remove particular words or phrases in messages
  4. Setup redirections from/to bots, groups, or people each direction
  5. The FREE version only offers up to 10 active redirections on your list. I would like the ability for PAID subscribers to buy package of 50 additional redirections. Admin has the ability to specify any # of redirections. Limit up to 100,000 redirections. No limit – unlimited would be great to have as well.

WORKFLOW

  1. Setup redirections from Bot, Channel (not an Admin), user --> Bot, Channel or user. Will need to Connect Telegram account to Bot for this to work from what I’ve gathered in my Research. Connect to telegram account to Bot also required to enable transformation & copy-paste forwarding feature.
  2. Enable transformation to replace a particular word from message (e.g. Tier: Gold -> Plan: Platinum)
  3. Enable copy-paste of Forwarding to remove "Forwarded from" header (aka "link to the sourcing channel") from redirected messages – hiding source
  4. Apply Filter to remove images, links, hashtags or certain keywords
  5. Apply Filter to forward a message containing a specific keyword/phrase to specific Bot, Channel or user– redirection.
  • Example#1: if message contains the keyword/phrase: “Plan: Platinum” -> Channel #1, Bot#1 or user#1.
  • Example#2: if message contains the keyword/phrase: “Plan: Gold” -> Channel#2, Bot#2 or user#2.
  • Example#3: if message contains the keyword/phrase: “Plan: Silver” -> Channel#3, Bot#3 or user#3.

Filters (/filter)

Command format:

/filter FILTER_NAME REDIRECTION_ID STATE

Available filters:

  • photos
  • documents
  • audios
  • stickers
  • videos
  • links
  • hashtags
  • contains
  • notContains

Set commands

Message this to @botfather

add - Add Redirection
activate - Activate redirection
deactivate - Deactivate redirection
list - List redirection
remove - Remove redirection
filter - Add Filter
filters - Get filter
help - Get help
transform - Add Transformation
transforms - List Transformation for a given redirection
transformrank - Swap rank of transformations for a given redirection
transformremove - Remove transformation

↪️wait-for.sh

#!/bin/sh
# The MIT License (MIT)
#
# Copyright (c) 2017 Eficode Oy
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
set -- "$@" -- "$TIMEOUT" "$QUIET" "$HOST" "$PORT" "$result"
TIMEOUT=15
QUIET=0
echoerr() {
  if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi
}
usage() {
  exitcode="$1"
  cat << USAGE >&2
Usage:
  $cmdname host:port [-t timeout] [-- command args]
  -q | --quiet                        Do not output any status messages
  -t TIMEOUT | --timeout=timeout      Timeout in seconds, zero for no timeout
  -- COMMAND ARGS                     Execute command with args after the test finishes
USAGE
  exit "$exitcode"
}
wait_for() {
 if ! command -v nc >/dev/null; then
    echoerr 'nc command is missing!'
    exit 1
  fi
  while :; do
    nc -z "$HOST" "$PORT" > /dev/null 2>&1
    
    result=$?
    if [ $result -eq 0 ] ; then
      if [ $# -gt 6 ] ; then
        for result in $(seq $(($# - 6))); do
          result=$1
          shift
          set -- "$@" "$result"
        done
        TIMEOUT=$2 QUIET=$3 HOST=$4 PORT=$5 result=$6
        shift 6
        exec "$@"
      fi
      exit 0
    fi
    if [ "$TIMEOUT" -le 0 ]; then
      break
    fi
    TIMEOUT=$((TIMEOUT - 1))
    sleep 1
  done
  echo "Operation timed out" >&2
  exit 1
}
while :; do
  case "$1" in
    *:* )
    HOST=$(printf "%s\n" "$1"| cut -d : -f 1)
    PORT=$(printf "%s\n" "$1"| cut -d : -f 2)
    shift 1
    ;;
    -q | --quiet)
    QUIET=1
    shift 1
    ;;
    -q-*)
    QUIET=0
    echoerr "Unknown option: $1"
    usage 1
    ;;
    -q*)
    QUIET=1
    result=$1
    shift 1
    set -- -"${result#-q}" "$@"
    ;;
    -t | --timeout)
    TIMEOUT="$2"
    shift 2
    ;;
    -t*)
    TIMEOUT="${1#-t}"
    shift 1
    ;;
    --timeout=*)
    TIMEOUT="${1#*=}"
    shift 1
    ;;
    --)
    shift
    break
    ;;
    --help)
    usage 0
    ;;
    -*)
    QUIET=0
    echoerr "Unknown option: $1"
    usage 1
    ;;
    *)
    QUIET=0
    echoerr "Unknown argument: $1"
    usage 1
    ;;
  esac
done
if ! [ "$TIMEOUT" -ge 0 ] 2>/dev/null; then
  echoerr "Error: invalid timeout '$TIMEOUT'"
  usage 3
fi
if [ "$HOST" = "" -o "$PORT" = "" ]; then
  echoerr "Error: you need to provide a host and port to test."
  usage 2
fi
wait_for "$@"

↪️.гитиньоре

bot/node_modules
agent/config/synaptic.py
agent/.venv/
*.db-journal
__pycache__
.env
agent/session/*
!.keep
*.db

↪️docker-compose.yml

version: '3.1'
services:
  node-bot:
    build: ./bot
    restart: always
    image: telegram-forwarder-node-bot
    environment:
      AGENT_PORT: 3000
      AGENT_HOSTNAME: python-agent
      TG_API_KEY: '${TG_API_KEY}'
      TG_BOT_USERNAME: '${TG_BOT_USERNAME}'
      DB_HOST: postgres-db
      NTBA_FIX_319: 1
    depends_on:
      - postgres-db
    command: ["./wait-for.sh", "postgres-db:5432", "--", "node", "bot.js"]
  python-agent:
    build: './agent'
    restart: always
    image: 'telegram-forwarder-python-agent'
    depends_on:
      - postgres-db
      - node-bot
    environment:
      API_PORT: 3000
      TG_API_ID: '${TG_API_ID}'
      TG_HASH_ID: '${TG_HASH_ID}'
      DB_HOST: postgres-db
    ports:
      - '3000:3000'
    volumes:
      - './agent/session:/telegram-python-agent/session'
  postgres-db:
    image: postgres:alpine
    environment:
      POSTGRES_PASSWORD: mysecretpassword
      POSTGRES_DB: telegram
    volumes:
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - '5432:5432'

↪️инициализация.sql

— Use this file to run sql queries on database intialization