'use strict';

function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }

var EventEmitter = _interopDefault(require('events'));
var https = _interopDefault(require('https'));
var querystring = _interopDefault(require('querystring'));
var path = _interopDefault(require('path'));
var qs = _interopDefault(require('qs'));
var fetch$1 = _interopDefault(require('node-fetch'));
var url = require('url');
var fs = _interopDefault(require('fs'));
var mime = _interopDefault(require('mime-types'));
var FormData = _interopDefault(require('form-data'));
var http = _interopDefault(require('http'));
var bodyParser = _interopDefault(require('body-parser'));
var WS = _interopDefault(require('ws'));
var encoding = _interopDefault(require('encoding'));
var CookieStore = _interopDefault(require('tough-cookie-file-store'));
var toughCookie = require('tough-cookie');
var ProxyAgent = _interopDefault(require('proxy-agent'));

class EventContext {
  constructor (type, event = {}) {
    this.type = type;
    this.moment = new Date();
    this.event = event;
  }
  toString () {
    return `[${this.moment.toString()}]\n${this.message ? this.message : JSON.stringify(this, null, '  ')}`
  }
}

class Debugger extends EventEmitter {
  constructor (...props) {
    super(...props);

    this._onRequest = this._onRequest.bind(this);
    this._onResponse = this._onResponse.bind(this);

    this.on(Debugger._toRaw(Debugger.EVENT_RESPONSE_TYPE), this._onResponse);
    this.on(Debugger._toRaw(Debugger.EVENT_REQUEST_TYPE), this._onRequest);
  }

  static _toUpper (s) {
    return String(s).replace(/^(.*){0,1}/, x => x[0].toUpperCase() + x.slice(1))
  }

  static _toRaw (s) {
    return 'raw' + Debugger._toUpper(s)
  }

  _onResponse (responseEvent = {}) {
    return this.emit('response', new EventContext(Debugger.EVENT_RESPONSE_TYPE, responseEvent))
  }

  _onRequest (requestEvent = {}) {
    return this.emit('request', new EventContext(Debugger.EVENT_REQUEST_TYPE, requestEvent))
  }
}

const EVENT_REQUEST_TYPE = 'request';
const EVENT_RESPONSE_TYPE = 'response';

const VKResponseReturner = function (staticMethods, dataResponse_, returnConstructor) {
  let response_ = dataResponse_;

  let res = response_.response;

  if (res === undefined || res === null) res = response_;

  let constructorName = (res).constructor.name;
  let Constructor = global[constructorName];

  if (!Constructor) Constructor = Object;

  class VKResponse extends Constructor {
    constructor (res) {
      if ((staticMethods.isString(res) || !isNaN(res) || res instanceof Boolean) && constructorName !== 'Array') {
        super(res);
      } else if (Array.isArray(res) || constructorName === 'Array') {
        super(...res);
        res.forEach((a, i) => {
          this[i] = a;
        });
      } else if (staticMethods.isObject(res)) {
        super();

        let self = this;

        let _props = {
          response: res
        };

        let canChanged = ['response'];

        for (let prop in _props) {
          let settings = {
            value: _props[prop]
          };

          if (canChanged.indexOf(prop) !== -1) {
            settings.configurable = true;
          }

          Object.defineProperty(self, prop, settings);
        }

        // Use session data with methods
        for (let prop in self.response) {
          Object.defineProperty(self, prop, {
            enumerable: true,
            configurable: true,
            value: self.response[prop]
          });
        }
      } else {
        super(res);
      }

      return this
    }

    getFullResponse () {
      return response_
    }
  }

  if (returnConstructor) {
    return VKResponse
  }

  return new VKResponse(res)
};

const PROTOCOL = 'https';
const BASE_DOMAIN = 'vk.com';

var configuration = {

  api_v: '5.101',
  reauth: false,
  save_session: true,
  session_file: path.join(__dirname, './vksession'),

  PROTOCOL,
  BASE_DOMAIN,
  MOBILE_SUBDOMAIN: 'm',

  BASE_CALL_URL: PROTOCOL + '://' + 'api.' + BASE_DOMAIN + '/method/',
  BASE_OAUTH_URL: PROTOCOL + '://' + 'oauth.' + BASE_DOMAIN + '/',

  ANDROID_CLIENT_ID: '2274003',
  ANDROID_CLIENT_SECRET: 'hHbZxrka2uZ6jB1inYsH',

  HTTP_CLIENT: {
    USER_AGENT: 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
    COOKIE_PATH: path.join(__dirname, '.evk-cookies.json')
  },

  DEFAULT_UTILS: {},
  DEFAULT_USER_AGENT: 'VKAndroidApp/6.2-5112 (Android 6.0; SDK 23; arm64-v8a; alps Razar; ru; 1280x720)'
};

// This is an object error for know: code, message, desciption and other information from stack

class VKResponseError extends Error {
  constructor (message, code = 0, request = {}) {
    super(message); // generate message

    this.error_code = code;
    this.request_params = request;
    this.error_msg = message || code;
    this.name = 'VKResponseError';
  }
}

class StaticMethods {
  constructor (settings = {}, evkParams = {}) {
    this.params = evkParams;
    this.settings = settings;

    if (evkParams.mode.name === 'highload') {
      this._requests = {};
      this.canComplete = true;

      evkParams.mode.timeout = Number(evkParams.mode.timeout);

      if (!evkParams.mode.timeout) {
        evkParams.mode.timeout = 15;
      }
    }
  }

  static createExecute (method = '', params = {}) {
    params.v = undefined;
    params.lang = undefined;
    return `API.${method}(${JSON.stringify(params)})`
  }

  static isString (n) {
    return Object.prototype.toString.call(n) === '[object String]'
  }

  static isObject (n) {
    return Object.prototype.toString.call(n) === '[object Object]'
  }

  static checkJSONErrors (vkr, reject) {
    let self = this;

    try {
      vkr = Object.prototype.toString.call(vkr) === '[object Object]' ? vkr : JSON.parse(vkr);

      let err = self.checkErrors(vkr);

      if (err) {
        if (typeof err === 'string') {
          // new error
          err = new Error(err);
        }

        reject(err);
        return false
      }

      return VKResponseReturner(self, vkr)
    } catch (e) {
      if (e.name === 'SyntaxError') {
        let err = new Error('Server sent not a json object (' + vkr + ')');

        return reject(err)
      }

      return reject(new Error(e))
    }
  }

  static urlencode (object = {}) {
    let self = this;

    return Object.keys(object)
      .map(prop =>
        prop + '=' + (
          (self.isObject(object[prop]))
            ? (encodeURIComponent(JSON.stringify(object[prop]))) : encodeURIComponent(object[prop])
        )
      )
      .join('&')
  }

  static checkErrors (vkr) {
    try {
      if (vkr.error) {
        if (vkr.error === 'need_captcha' || vkr.error.error_code === 14) {
          return JSON.stringify(vkr)
        } else if (vkr.error === 'need_validation') {
          if (vkr.ban_info) {
            return vkr.error_description
          } else {
            let type = 'sms';

            if (vkr.validation_type.match('app')) {
              type = 'app';
            }

            return {
              error: `Please, enter your ${type} code in code parameter!`,
              error_code: vkr.error,
              validation_type: vkr.validation_type,
              validation_sid: vkr.validation_sid,
              redirect_uri: vkr.redirect_uri
            }
          }
        } else if (vkr.error.error_code === 17) {
          return {
            redirect_uri: vkr.error.redirect_uri,
            error: vkr.error.error_msg,
            error_code: vkr.error.error_code
          }
        }

        if (vkr.error.error_msg) {
          return new VKResponseError(vkr.error.error_msg, vkr.error.error_code, vkr.error.request_params)
        } else if (vkr.error.message) {
          return new VKResponseError(vkr.error.message, vkr.error.code, vkr.error.params)
        } else {
          return new VKResponseError(vkr.error_description ? vkr.error_description : vkr.error, vkr.error_code ? vkr.error_code : vkr.error)
        }
      }
    } catch (e) {
      return e
    }
  }

  static async call (methodName, data = {}, methodType = 'get', debuggerIS = null, Agent, settings = {}) {
    let self = this;

    return new Promise((resolve, reject) => {
      let methodTypeLower = methodType.toString().toLocaleLowerCase();

      if (methodTypeLower !== 'post') {
        methodType = 'get';
      }

      if (!methodName) {
        return reject(new Error('Put method name in your call request!'))
      }

      if (!data.v) {
        data.v = configuration.api_v;
      }

      if (!settings.userAgent) {
        settings.userAgent = configuration.DEFAULT_USER_AGENT;
      }

      let callParams = {
        url: configuration.BASE_CALL_URL + methodName
      };

      let data2 = Object.assign({}, data);

      if (methodType === 'post') {
        // prepare post request
        callParams.agent = Agent;

        callParams.body = qs.stringify(data);
        callParams.headers = {
          'Content-Type': 'application/x-www-form-urlencoded',
          'User-agent': settings.userAgent
        };

        if (settings.debug) {
          settings.debug(EVENT_REQUEST_TYPE, {
            url: callParams.url,
            method: 'POST',
            query: data,
            section: 'vk.call'
          });
        }
      }

      if (debuggerIS) {
        try {
          debuggerIS.push('fullRequest', callParams);
        } catch (e) {
          return reject(new Error('No have a complite debuggerIS'))
        }
      }

      if (methodType === 'get') {
        data = querystring.stringify(data);

        let options = {
          host: 'api.' + configuration.BASE_DOMAIN,
          agent: Agent,
          path: '/method/' + methodName + '?' + data,
          headers: {
            'User-Agent': settings.userAgent
          }
        };

        if (settings.debug) {
          settings.debug(EVENT_REQUEST_TYPE, {
            url: 'https://' + options.host + options.path,
            method: 'GET',
            query: data,
            section: 'vk.call'
          });
        }

        let req;

        const abortListener = () => {
          req.abort();
          let err = new Error('Request aborted!');
          err.code = 'aborted';
          err.type = err.code;
          return reject(err)
        };

        req = https.get(options, (res) => {
          if (settings.signal) {
            settings.signal.removeEventListener('abort', abortListener);
          }

          let vkr = '';
          res.setEncoding('utf8');
          res.on('data', (chu) => {
            vkr += chu;
          });
          res.on('end', () => { return parseResponse(vkr) });
        }).on('error', (e) => {
          if (settings.signal) {
            settings.signal.removeEventListener('abort', abortListener);
          }

          try {
            reject(e);
          } catch (err) {
            throw e
          }
        });

        if (settings.signal) {
          settings.signal.addEventListener('abort', abortListener);
        }
      } else {
        callParams.method = 'POST';
        callParams.signal = settings.signal;

        fetch$1(callParams.url, callParams).then(async (res) => {
          res = await res.json();
          return parseResponse(res)
        }).catch(e => {
          if (e.type === 'aborted') e.code = e.type;
          reject(e);
        });
      }

      async function parseResponse (vkr) {
        if (vkr) {
          if (debuggerIS) {
            try {
              debuggerIS.push('response', vkr);
            } catch (e) {}
          }

          if (settings.debug) {
            settings.debug(EVENT_RESPONSE_TYPE, {
              body: vkr,
              section: 'vk.call'
            });
          }

          let json = self.checkJSONErrors(vkr, reject);

          if (json) {
            json.queryData = data2;

            return resolve(json)
          } else {
            return reject(new Error("JSON is not valid... oor i don't know"))
          }
        } else {
          return reject(new Error(`Empty response ${vkr}`))
        }
      }
    })
  }

  static randomId (peerId = 0) {
    return parseInt(new Date().getTime() + '' + peerId + '' + Math.floor(Math.random() * 10000), 10)
  }

  async _completeExecute (token = '', Agent, settings) {
    if (!token) throw new Error('Unused token')

    let requests = this._requests[token];

    if (!requests.stack) throw new Error('Unknow error')

    let execCode;
    let execs = [];
    let execsData = [];
    let stack = [...requests.stack];

    requests.stack = [];
    this.canComplete = true;

    stack.forEach((requestExec) => {
      execs.push(requestExec.exec);
      execsData.push(requestExec.data);
    });

    execCode = `return [${execs.join(',')}];`;

    let data = {
      access_token: token,
      v: this.params.api_v,
      code: execCode
    };

    if (this.params.lang !== 'undefined') data.lang = this.params.lang;
    StaticMethods.call('execute', data, 'post', null, Agent, settings).then((vkr) => {
      let vkrFull = vkr.getFullResponse();
      let errorIndex = 0;
      vkr.forEach((val, i) => {
        let req = stack[i];

        if (val === false) {
          if (vkrFull.execute_errors) {
            let err = vkrFull.execute_errors[errorIndex];
            if (err && execs[i].match(err.method)) { // "API.messages.send()".match("messages.send")
              err = new VKResponseError(err.error_msg, err.error_code, execsData[i].data);
              return req.reject(err)
            }
          }

          let err = new Error('Error occured in execute method');

          err.response = val;
          err.request = req;
          req.reject(err);
          errorIndex += 1;
        } else {
          let vkr = VKResponseReturner(StaticMethods, val);
          req.resolve(vkr);
        }
      });
    }).catch(err => {
      let req = stack[stack.length - 1];

      err.highload = {
        stack,
        settings,
        data
      };

      req.reject(err);
    });
  }

  async initHighLoadRequest (method, data, type, debuggerIS, Agent) {
    let self = this;

    return new Promise((resolve, reject) => {
      // disable custom version and language in execute methods
      data.v = undefined;
      data.lang = undefined;

      data = JSON.parse(JSON.stringify(data));

      let accessToken = data.access_token;
      data.access_token = undefined;

      let requests = self._requests[accessToken];

      if (!requests) {
        requests = self._requests[accessToken] = {
          stack: [],
          timeoutId: 0
        };
      }

      if (requests.timeoutId) {
        clearTimeout(requests.timeoutId);
      }

      requests.stack.push({
        exec: self.createExecute(method, data),
        data: {
          method: method,
          data: data,
          token: accessToken
        },
        resolve,
        reject
      });

      function complete () {
        if (self.canComplete) {
          self.canComplete = false;
          self._completeExecute(accessToken, Agent, self.settings);
        }
      }

      if (requests.stack.length === 25) {
        complete();
        return
      }

      requests.timeoutId = setTimeout(function () {
        complete();
      }, self.params.mode.timeout);
    })
  }

  createExecute () {
    return StaticMethods.createExecute(...arguments)
  }

  async call () {
    if (this.params.mode.name === 'highload' && arguments[0] !== 'execute') {
      return this.initHighLoadRequest(...arguments)
    }

    return StaticMethods.call(...arguments, this.settings)
  }
}

let stack = [];

class RequestsDebugger extends EventEmitter {
  // Push to debugger
  async push (type = 'response', data) {
    let self = this;

    let logData = {
      type: type.toString(),
      data: data
    };

    stack.splice(0, 1);
    stack.push(logData);

    self.emit('push', logData);

    return (stack.length - 1)
  }

  // Get last log from stack
  lastLog () {
    return stack[stack.length - 1]
  }
}

class EasyVKUploader {
  constructor (vk) {
    let self = this;
    self._vk = vk;
  }

  /*
   *
   *  Function for upload file from other server only by fileUrl
   *
   */
  async uploadFetchedFile (url, fileUrl, fieldName = 'file', paramsUpload = {}) {
    return new Promise((resolve, reject) => {
      if (!url || !StaticMethods.isString(url)) {
        return reject(this._vk._error('is_not_string', {
          parameter: 'url',
          method: 'uploadFetchedFile',
          format: 'http(s)://www.domain.example.com/path?request=get'
        }))
      }

      if (!fileUrl || (!StaticMethods.isString(fileUrl) && !StaticMethods.isObject(fileUrl))) {
        return reject(this._vk._error('is_not_string', {
          parameter: 'fileUrl',
          method: 'uploadFetchedFile',
          format: 'https://vk.com/images/community_100.png'
        }))
      }

      if (fieldName) {
        if (!StaticMethods.isString(fieldName)) {
          return reject(this._vk._error('is_not_string', {
            parameter: 'fieldName',
            method: 'uploadFetchedFile',
            required: false
          }))
        }
      }

      if (!StaticMethods.isObject(paramsUpload)) {
        paramsUpload = {};
      }

      if (StaticMethods.isString(fileUrl)) {
        fileUrl = {
          url: fileUrl
        };
      }

      let fetchingFileUrl = fileUrl.url;
      let filename = fileUrl.name || fetchingFileUrl.split('/').pop().split('#')[0].split('?')[0];

      if (!filename) {
        return reject(this._vk._error('is_not_string', {
          parameter: 'fileUrl.name',
          method: 'uploadFile',
          format: 'example.jpeg or example.rar'
        }))
      }

      return fetch$1(fetchingFileUrl, {
        agent: this._agent
      }).then(async (res) => {
        let buff = await res.buffer();

        let form = new FormData();

        form.append(fieldName, buff, {
          filename: fieldName + '.' + mime.extension(res.headers.get('content-type') || 'text/plain')
        });

        return fetch$1(url, {
          method: 'POST',
          body: form,
          agent: this._agent
        }).then(async (response) => {
          let vkr = await response.json();

          if (vkr) {
            if (form.custom) {
              return resolve(vkr)
            } else {
              let json = StaticMethods.checkJSONErrors(vkr, reject);

              if (json) {
                return resolve(vkr)
              } else {
                return reject(this._vk._error('invalid_response', {
                  response: response
                }))
              }
            }
          } else {
            return reject(this._vk._error('empty_response', {
              response: response
            }))
          }
        })
      })
    })
  }

  /*
   *
   *  Function for upload file from local machine
   *
   */
  async uploadFile (url, filePath, fieldName = 'file', paramsUpload = {}) {
    let self = this;
    return new Promise((resolve, reject) => {
      if (!url || !StaticMethods.isString(url)) {
        return reject(self._vk._error('is_not_string', {
          parameter: 'url',
          method: 'uploadFile',
          format: 'http(s)://www.domain.example.com/path?request=get'
        }))
      }

      if (!filePath || !StaticMethods.isString(filePath)) {
        if (!(filePath instanceof fs.ReadStream)) {
          return reject(self._vk._error('is_not_string', {
            parameter: 'filePath',
            method: 'uploadFile',
            format: path.join(__dirname, '..', 'example', 'path')
          }))
        }
      }

      if (fieldName) {
        if (!StaticMethods.isString(fieldName)) {
          return reject(self._vk._error('is_not_string', {
            parameter: 'fieldName',
            method: 'uploadFile',
            required: false
          }))
        }
      }

      if (!StaticMethods.isObject(paramsUpload)) {
        paramsUpload = {};
      }

      let stream, data;

      stream = (filePath instanceof fs.ReadStream) ? filePath : fs.createReadStream(filePath);
      data = new FormData();

      Object.keys(paramsUpload)
        .forEach((param) => {
          if (param !== fieldName) {
            data.append(param, paramsUpload[param]);
          }
        });

      stream.on('error', (error) => {
        return reject(new Error(error))
      });

      stream.on('open', () => {
        data.append(fieldName, stream);
        return fetch$1(url, {
          method: 'POST',
          body: data,
          agent: self._agent,
          headers: data.getHeaders()
        }).then(async (response) => {
          let vkr = await response.json();

          if (vkr) {
            if (data.custom) {
              return resolve(vkr)
            } else {
              let json = StaticMethods.checkJSONErrors(vkr, reject);

              if (json) {
                return resolve(vkr)
              } else {
                return reject(self._vk._error('invalid_response', {
                  response: response
                }))
              }
            }
          } else {
            return reject(self._vk._error('empty_response', {
              response: response
            }))
          }
        }).catch(reject)
      });
    })
  }

  async upload ({
    getUrlMethod,
    saveMethod,
    file,
    getUrlParams = {},
    saveParams = {},
    uploadParams = {},
    isWeb = false,
    fieldName = 'file',
    uploadUrlField = 'upload_url'
  }, returnAll = false) {
    return this.getUploadURL(getUrlMethod, getUrlParams, true).then(({ vkr }) => {
      let url = vkr[uploadUrlField];
      let uploadMethod = isWeb ? 'uploadFetchedFile' : 'uploadFile';

      return this[uploadMethod](url, file, fieldName, uploadParams).then(vkr => {
        return this._vk.call(saveMethod, Object.assign(saveParams, vkr))
      })
    })
  }

  async getUploadURL (methodName, params = {}, returnAll = false) {
    let self = this;

    return new Promise((resolve, reject) => {
      if (!StaticMethods.isObject(params)) {
        return reject(self._vk._error('is_not_object', {
          parameter: 'params',
          method: 'getUploadURL'
        }))
      }

      self._vk.call(methodName, params).then((vkr) => {
        if (vkr.upload_url) {
          if (returnAll) {
            return resolve({
              url: vkr,
              vkr: vkr
            })
          } else {
            return resolve(vkr.upload_url)
          }
        } else {
          return reject(self._vk._error('upload_url_error', {
            response: vkr
          }))
        }
      }, reject);
    })
  }
}

class MiddlewaresMechanism {
  constructor (instance = Object) {
    // this.instance = instance;

    let self = this;

    self.middleWares = [async (data) => {
      let next = data.next;
      data.next = undefined;
      await next(data);
    }];

    instance.use = function (middleWare = null, rejectMiddleware = Function) {
      if (middleWare && typeof middleWare === 'function') {
        self.middleWares.push(async (p) => {
          let next = p.next;
          return new Promise((resolve, reject) => {
            middleWare(p).then(resNext => {
              if (resNext) return resolve(p)
              p.next = undefined;
              return resolve(p)
            }).catch(async (e) => {
              if (rejectMiddleware && typeof rejectMiddleware === 'function') {
                rejectMiddleware(e);
              } else {
                console.log(e);
              }

              return resolve(await next())
            });
          })
        });
      }

      return this
    };
  }

  async run (thread = {}) {
    let self = this;

    let setupedMiddleWare = 0;

    let context = {};

    context.next = next;
    context.thread = thread;

    async function next () {
      let changedThread = context.thread;

      // Call to next middleware
      setupedMiddleWare += 1;

      for (let prop in changedThread) {
        if (context.thread[prop] === undefined) { // if it was deleted by middleware, not changed
          context.thread[prop] = changedThread[prop]; // need add deleted property
        }
      }

      // so, now we can use changed data in this new middleware
      context.next = next;
      if (self.middleWares[setupedMiddleWare]) {
        let res = await self.middleWares[setupedMiddleWare](context);
        // console.log('Res from ', setupedMiddleWare)
        if (!res) return context
        return res
      } else {
        return context
      }
    }

    if (self.middleWares.length) {
      let res = await self.middleWares[setupedMiddleWare](context);

      if (typeof res !== 'object' || !res) {
        res = {};
      }

      for (let prop in context.thread) {
        if (res[prop] === undefined) {
          res[prop] = context.thread[prop];
        }
      }

      return res
    }

    return thread
  }
}

class AbortSignal extends EventEmitter {
  constructor () {
    super();
    this.aborted = false;
  }

  addEventListener (...args) {
    return this.on(...args)
  }

  removeEventListener (...args) {
    this.removeListener(...args);
    return this
  }
}

class AbortController {
  constructor () {
    this.signal = new AbortSignal();
  }

  async abort () {
    this.signal.aborted = true;
    return this.signal.emit('abort')
  }
}

class LongPollConnection extends EventEmitter {
  constructor (lpSettings, vk) {
    super();

    let self = this;

    self.config = lpSettings;
    self._vk = vk;
    self.userListeners = {};
    self.abortController = new AbortController();

    self.supportEventTypes = {
      '4': 'message',
      '8': 'friendOnline',
      '9': 'friendOffline',
      '51': 'editChat',
      '61': 'typeInDialog',
      '62': 'typeInChat',
      '3': 'changeFlags'
    };

    self._middlewaresController = new MiddlewaresMechanism(self);

    init();

    async function reconnect () {
      return self._vk.call('messages.getLongPollServer', self.config.userConfig.forGetLongPollServer).then((vkr) => {
        self.config.longpollServer = vkr.server;
        self.config.longpollTs = vkr.ts;
        self.config.longpollKey = vkr.key;

        return init() // reconnect with new parameters
      }).catch((err) => {
        self.emit('reconnectError', new Error(err));
      })
    }

    async function init () {
      let server, forLongPollServer, _w;
      let httpsPref = 'https://';

      if (self.config.longpollServer.substr(0, httpsPref.length) !== httpsPref) {
        self.config.longpollServer = httpsPref + self.config.longpollServer;
      }

      server = `${self.config.longpollServer}`;

      forLongPollServer = {};
      _w = null;

      forLongPollServer.act = 'a_check';
      forLongPollServer.key = self.config.longpollKey;
      forLongPollServer.ts = self.config.longpollTs;
      forLongPollServer.mode = self.config.userConfig.forLongPollServer.mode;
      forLongPollServer.version = self.config.userConfig.forLongPollServer.version;
      forLongPollServer.wait = self.config.userConfig.forLongPollServer.wait;

      if (isNaN(forLongPollServer.mode)) {
        forLongPollServer.mode = 8 | 2;
      }

      if (isNaN(forLongPollServer.version)) {
        forLongPollServer.version = '2';
      }

      _w = Number(forLongPollServer.wait);

      let params = {
        timeout: (_w * 1000) + (1000 * 3),
        headers: {
          'connection': 'keep-alive',
          'content-type': 'application/x-www-form-urlencoded'
        },
        agent: self._vk.agent,
        method: 'GET',
        signal: self.abortController.signal
      };

      server = server + '?' + qs.stringify(forLongPollServer);

      self._debug({
        type: 'longPollParamsQuery',
        data: params
      });

      self._vk.debug(EVENT_REQUEST_TYPE, {
        url: server,
        query: forLongPollServer,
        method: 'GET',
        section: 'longpoll'
      });

      self.lpConnection = fetch$1(server, params).catch((e) => {
        return reconnect()
      }).then(async (res) => {
        if (!res) return false
        if (!res.json) return res
        res = await res.json();

        if (self._vk._debugger) {
          try {
            self._vk._debugger.push('response', res);
          } catch (e) {
            // Ignore
          }
        }

        self._vk.debug(EVENT_RESPONSE_TYPE, { body: res, section: 'longpoll' });

        self._debug({
          type: 'pollResponse',
          data: res
        });

        let vkr = res;

        if (vkr.ts) {
          if (vkr.ts) {
            self.config.longpollTs = vkr.ts;
          }

          if (vkr.updates && vkr.updates.length) {
            vkr.updates.forEach((upd, i) => {
              vkr.updates[i] = {
                type: upd[0],
                object: upd
              };
            });

            self._middlewaresController.run(vkr).then(() => {
              self._checkUpdates(vkr.updates);
            });
          }

          return init()
        }

        if (vkr.failed) {
          if (vkr.failed === 1) { // update ts
            if (vkr.ts) {
              self.config.longpollTs = vkr.ts;
            }

            return init()
          } else if ([2, 3].indexOf(vkr.failed) !== -1) { // need reconnect
            self._vk.call('messages.getLongPollServer', self.config.userConfig.forGetLongPollServer).then((vkr) => {
              self.config.longpollServer = vkr.server;
              self.config.longpollTs = vkr.ts;
              self.config.longpollKey = vkr.key;

              return init() // reconnect with new parameters
            }).catch((err) => {
              self.emit('reconnectError', new Error(err));
            });
          } else {
            return self.emit('failure', vkr)
          }
        }

        if (vkr.error) {
          self.emit('error', vkr.error);
          return reconnect()
        }
      }).catch(err => {
        if (err.toString().match('TIMEDOUT') || err.toString().match('ENOENT') || err.toString().match('timeout')) {
          return reconnect()
        }
        return self.emit('error', err)
      });
    }
  }

  async _debug () {
    if (this._debugg) {
      this._debugg(...arguments);
    }
  }

  async _checkUpdates (updates) {
    let self = this;

    let len = updates.length;

    for (let updateIndex = 0; updateIndex < len; updateIndex++) {
      let typeEvent = updates[updateIndex].type.toString();

      self.emit('update', updates[updateIndex].object);
      if (self.supportEventTypes[typeEvent]) {
        typeEvent = self.supportEventTypes[typeEvent];
        self.emit(typeEvent, updates[updateIndex].object);
      }

      try {
        if (self.userListeners[typeEvent]) {
          self.userListeners[typeEvent](updates[updateIndex].object);
        }
      } catch (e) {
        self.emit('error', e);
      }
    }
  }

  /**
   *
   *  If my SDK not support certain event it doesn't mean that my SDK not support it :D
   *  You can add yours listeners with this function.
   *
   *  Docs: vk.com/dev/using_longpoll
   *
   *  @param {Number} eventCode number of event which you can find on the docs page
   *  @param {Function} handler is a handler function
   *
   */

  async addEventCodeListener (eventCode, handler) { // Only for create new event listeneres (if there are not in default listeners, you can get a code and add it!)
    let self = this;

    return new Promise((resolve, reject) => {
      if (isNaN(eventCode)) {
        return reject(self._vk._error('is_not_number', {
          'where': 'LongPoll.addEventCodeListener',
          'parameter': 'eventCode'
        }))
      } else if (Object.prototype.toString.call(handler) !== '[object Function]') {
        return reject(self._vk._error('is_not_function', {
          'where': 'LongPoll.addEventCodeListener',
          'parameter': 'handler'
        }))
      } else {
        eventCode = eventCode.toString();

        if (!self.supportEventTypes[eventCode]) {
          self.supportEventTypes[eventCode] = eventCode;
          self.userListeners[eventCode] = handler;
        } else {
          return reject(self._vk._error('longpoll_api', {}, 'event_already_have'))
        }
      }
    })
  }

  async close () {
    let self = this;

    return new Promise((resolve, reject) => {
      if (self.lpConnection) {
        self.emit('close', {
          time: new Date().getTime()
        });

        return resolve(self.abortController.abort())
      } else {
        return reject(self._vk._error('longpoll_api', {}, 'not_connected'))
      }
    })
  }

  debug (debugg) {
    let self = this;

    console.warn('[Deprecated method warning] \nThis method will be deprecated in next releases. Please, use new easyvk.Debugger() and set it up in the easyvk configuration like params.debug = myDebugger');

    if (Object.prototype.toString.call(debugg).match(/function/i)) {
      self._debugg = debugg;
    } else {
      return false
    }

    return self
  }
}

class LongPollConnector {
  constructor (vk) {
    let self = this; // For the future
    self._vk = vk;
  }

  async connect (params = {}) {
    let self = this;

    return new Promise((resolve, reject) => {
      if (!StaticMethods.isObject(params)) {
        return reject(self._vk._error('is_not_object', {
          'where': 'LongPoll.connect',
          'parameter': 'params'
        }))
      } else {
        if (params.forGetLongPollServer) {
          if (!StaticMethods.isObject(params.forGetLongPollServer)) {
            params.forGetLongPollServer = {};
          }
        } else {
          params.forGetLongPollServer = {};
        }

        if (params.forLongPollServer) {
          if (!StaticMethods.isObject(params.forLongPollServer)) {
            params.forLongPollServer = {};
          }
        } else {
          params.forLongPollServer = {};
        }

        if (isNaN(params.forGetLongPollServer.lp_version)) {
          params.forGetLongPollServer.lp_version = '2';
        }

        if (isNaN(params.forLongPollServer.wait)) {
          params.forLongPollServer.wait = '25';
        }

        if (params.forGetLongPollServer.use_ssl !== 0) {
          params.forGetLongPollServer.use_ssl = 1;
        }

        self._vk.call('messages.getLongPollServer', params.forGetLongPollServer)
          .then((vkr) => {
            let forLongPoll = {
              longpollServer: vkr.server,
              longpollTs: vkr.ts,
              longpollKey: vkr.key,
              responseGetServer: vkr,
              userConfig: params
            };

            let con = new LongPollConnection(forLongPoll, self._vk);

            return resolve(con)
          }, reject);
      }
    })
  }
}

/*
 *  It's a Callbakc API module for EasyVK
 *  You can use it
 *
 *  Author: @ciricc
 *  License: MIT
 *
 */

class CallbackAPI extends EventEmitter {
  constructor (vk) {
    super();
    let self = this;
    self._vk = vk;
    self._middlewaresController = new MiddlewaresMechanism(self);
  }

  __initVKRequest (req, res) {
    let postData, self;

    self = this;
    postData = req.body;

    if (!postData.group_id) {
      res.status(400).send('only vk requests');
      return self.emit('eventEmpty', {
        postData: postData,
        description: "This request haven't group_id of event. Event name is empty"
      })
    }

    let group = self._cbparams.groups[postData.group_id.toString()];

    if (postData.type === 'confirmation') {
      if (group) {
        if (group.secret) { // If you use a password fro menage it
          if (postData.secret && postData.secret.toString() === group.secret.toString()) {
            res.status(200).send(group.confirmCode);
          } else {
            res.status(400).send('secret error');
            self.emit('secretError', {
              postData: postData,
              description: 'We got the secret key which no uses in your settings! If you want to add secret, set up it in secret parameter!'
            });
          }
        } else {
          res.status(200).send(group.confirmCode);
        }
      } else {
        res.status(400).send('not have this group');
        self.emit('confirmationError', {
          postData: postData,
          description: "You don't use this group, becouse we don't know this groupId"
        });
      }
    } else if (postData.type !== 'confirmation') {
      if (group) {
        if (group.secret) {
          if (postData.secret && postData.secret.toString() !== group.secret.toString()) {
            res.status(400).send('secret error');
            self.emit('secretError', {
              postData: postData,
              description: 'Secret from request and from your settings are not the same'
            });

            return
          } else if (!postData.secret) {
            res.status(400).send('secret error');
            self.emit('secretError', {
              postData: postData,
              description: 'Request has not a secret password, but you use it in this group'
            });

            return
          }
        }

        if (postData.type) {
          self.emit(postData.type, postData);
          res.status(200).send('ok');
        } else {
          res.status(400).send('invalid type event');
          self.emit('eventEmpty', {
            postData: postData,
            description: "This request haven't type of event. Event name is empty"
          });
        }
      } else {
        res.status(400).send('not have this group');
        self.emit('confirmationError', {
          postData: postData,
          description: "You don't use this group, becouse we don't know this groupId"
        });
      }
    } else {
      res.status(400).send('only vk requests');
    }
  }

  __init404Error (req, res) {
    res.status(404).send('Not Found');
  }

  async __initApp (params = {}) {
    let self = this;

    self._cbparams = params;

    return new Promise((resolve, reject) => {
      let { app } = params;
      let server;

      if (!app) throw new Error('You must have app parameter, like express application')

      app.use(bodyParser.json());

      app.post(params.path, (req, res) => {
        self.__initVKRequest(req, res);
      });

      app.get(params.path, (req, res) => {
        self.__init404Error(req, res);
      });

      server = http.createServer(app);

      this._server = server;
      server.listen(params.port || process.env.PORT || 3000);

      return resolve(true)
    })
  }
}

class CallbackAPIConnector {
  // Auto constructed by EasyVK
  constructor (vk) {
    let self = this;
    self._vk = vk;
  }

  /*
   *  This function is up your server for listen group events
   *
   *  @param {Object} callbackParams - Object for setup your server
   *  @param {Object[]} [callbackParams.groups] - Array of your groups which you want listen
   *  @param {String|Number} [callbackParams.groupId] - Group id which you want listen, if you use groups[] then it will be added too
   *  but you can't no input neither callbackParams.groups nor groupId and etc.
   *  You need select your group at least one way
   *  @param {String|Number} [callbackParams.confirmCode] - Your confirmation code. This code will be sended for confirmation query
   *  @param {String|Number} [callbackParams.secret] - Your secret code for one group, I am recommend you to use it for secure
   *  @param {String|Number} [callbackParams.port=(process.env.PORT || 3000)] - Port for http server, default is process.env.PORT || 3000
   *
   *  If you use many groups, you need separate (spread) groupId, secret and condirmCode parameters on objects in array of groups
   *  like { groups: [{groupId: ..., secret: ..., confirmCode: ...}, ...] }
   *
   *  @return {Promise}
   *  @promise up your server for listen group events
   *  @resolve {Object} - Object with web application, CallbackAPI connection object
   *  and EasyVK parameter:
   *  {vk: EasyVK, connection: CallbackAPI, web: expressApplication}
   *  @reject {Error} - express run and up server error
   *
   */

  async listen (callbackParams = {}) {
    let self = this;

    return new Promise((resolve, reject) => {
      if (callbackParams) {
        if (!StaticMethods.isObject(callbackParams)) {
          callbackParams = {};
        }
      }

      if (!Array.isArray(callbackParams.groups)) {
        callbackParams.groups = [];
      }

      if (!callbackParams.groupId) {
        if (self._vk.session && self._vk.session.group_id) {
          callbackParams.groupId = self._vk.session.group_id;
        }
      }

      if (callbackParams.groupId) { // If user wants only one group init
        if (!callbackParams.confirmCode && (self._vk.session && self._vk.session.group_id && callbackParams.groupId !== self._vk.session.group_id)) {
          return reject(new Error("You don't puted confirmation code"))
        }

        callbackParams.groups.push({
          confirmCode: callbackParams.confirmCode,
          groupId: callbackParams.groupId
        });

        if (callbackParams.secret) {
          callbackParams.groups[callbackParams.groups.length - 1].secret = callbackParams.secret;
        }
      }

      if (callbackParams.groups.length === 0) {
        return reject(new Error('Select a group for listen calls'))
      } else {
        let grTemp = {};
        let registered = [];

        callbackParams.groups.forEach(async (elem, index) => {
          let group = callbackParams.groups[index];

          if (!StaticMethods.isObject(group)) {
            return reject(new Error(`Group settings is not an object (in ${index} index)`))
          }

          if (!group.groupId) {
            if (self._vk.session && self._vk.session.group_id) {
              group.groupId = self._vk.session.group_id;
            }
          }

          if (!group.groupId || registered.indexOf(group.groupId) !== -1) {
            return reject(new Error(`Group id must be (groupId in ${index} index)`))
          }

          registered.push(group.groupId);

          if (!group.confirmCode) {
            if (self._vk.session && group.groupId === self._vk.session.group_id) {
              let confirmToken = await self._vk.call('groups.getCallbackConfirmationCode', {
                group_id: group.groupId
              });

              group.confirmCode = confirmToken.code;
            }
          }

          if (!group.confirmCode) {
            return reject(new Error(`Confirmation code must be (confirmCode in ${index} index)`))
          } else {
            grTemp[group.groupId.toString()] = group;
          }
        });

        callbackParams.groups = grTemp;
      }

      if (!callbackParams.path) {
        callbackParams.path = '/';
      }
      let cbserver = new CallbackAPI(self._vk);

      cbserver.__initApp(callbackParams).then((app) => {
        return resolve(cbserver)
      });
    })
  }
}

class StreamingAPIConnection extends EventEmitter {
  constructor (vk, session, wsc) {
    super();

    let self = this;

    self._vk = vk;
    self._session = session;
    self._wsc = wsc;
    self._urlHttp = `${configuration.PROTOCOL}://${self._session.server.endpoint}`;
    self._key = self._session.server.key;
    self.__initWebSocket();
  }

  __initWebSocket () {
    let self = this;

    self._wsc.on('error', (error) => {
      self.emit('error', new Error(error.toString()));
    });

    self._wsc.on('message', (message) => {
      self.__initMessage(message);
    });

    self._wsc.on('close', (r) => {
      self.emit('failure', `Connection closed ${(r) ? '(' + r + ')' : ''}`);
    });
  }

  __initMessage (msgBody) {
    var self = this;

    try {
      let body = JSON.parse(msgBody);
      if (parseInt(body.code) === 100) {
        self.emit(body.event.event_type, body.event);
        self.emit('pullEvent', body.event);
      } else if (body.code === 300) {
        self.emit('serviceMessage', body.service_message);
      }
    } catch (e) {
      self.emit('error', e);
    }
  }

  async close () {
    let self = this;

    return new Promise((resolve, reject) => {
      if (self._wsc) {
        return resolve(self._wsc.close())
      } else {
        return reject(new Error('WebSocket not connected'))
      }
    })
  }

  async __MRHTTPURL (method, json) {
    return new Promise((resolve, reject) => {
      method = method.toString().toLocaleLowerCase();

      let url = `${this._urlHttp}/rules`;
      json = {
        ...json,
        key: this._key
      };

      let queryParams = {
        method: method,
        body: method === 'get' ? null : JSON.stringify(json),
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        agent: this._vk.agent
      };

      if (this._vk && this._vk.debug) {
        this._vk.debug(EVENT_REQUEST_TYPE, {
          url,
          query: json,
          section: 'streamingAPI',
          method: method.toUpperCase()
        });
      }

      if (method === 'get') {
        url = url + '?' + qs.stringify(json);
      } else {
        url = url + '?key=' + this._key;
      }

      return fetch$1(url, queryParams).then(async (res) => {
        let vkr = await res.json();

        if (this._vk && this._vk._debugger) {
          try {
            this._vk._debugger.push('response', vkr);
          } catch (e) {
            // Ignore
          }
        }

        if (this._vk && this._vk.debug) {
          this._vk.debug(EVENT_RESPONSE_TYPE, {
            body: vkr,
            section: 'streamingAPI'
          });
        }

        if (vkr) {
          let json = StaticMethods.checkJSONErrors(vkr, reject);
          if (json) {
            return resolve(json)
          } else {
            return reject(new Error("JSON is not valid... oor i don't know"))
          }
        } else {
          reject(new Error(`Empty response ${vkr}`));
        }
      })
    })
  }

  async addRule (tag, rule) {
    let MRHTTPParams = {
      'rule': {
        'value': rule,
        'tag': tag
      }
    };
    return this.__MRHTTPURL('POST', MRHTTPParams)
  }

  async deleteRule (tag) {
    let MRHTTPParams = {
      'tag': tag
    };
    return this.__MRHTTPURL('DELETE', MRHTTPParams)
  }

  async getRules () {
    return this.__MRHTTPURL('GET', {})
  }

  async deleteAllRules () {
    let self = this;

    return new Promise((resolve, reject) => {
      // For begin - get All rules
      self.getRules().then((rules) => {
        rules = rules.rules;
        let i = 0;

        function del () {
          if (i === rules.length) {
            return resolve(true)
          }

          let rule = rules[i];

          self.deleteRule(rule.tag).then(() => {
            i++;

            setTimeout(() => {
              del();
            }, 600);
          }, reject);
        }

        if (rules) {
          del();
        } else {
          return resolve(true)
        }
      }, reject);
    })
  }

  async initRules (rulesObject = {}, callBackError) {
    let self = this;

    return new Promise((resolve, reject) => {
      if (!StaticMethods.isObject(rulesObject)) {
        return reject(new Error('rules must be an object'))
      }

      if (callBackError) {
        if (Object.prototype.toString.call(callBackError) !== '[object Function]') {
          return reject(new Error('callBackError must be function'))
        }
      }

      // For begin get all rules and then change/add/delete rules
      self.getRules().then((startedRules) => {
        let changedRules, stRulesObject, tags, addedRules, deletedRules;

        startedRules = startedRules.rules;

        if (!startedRules) {
          startedRules = [];
        }

        changedRules = [];
        addedRules = [];
        deletedRules = [];
        stRulesObject = {};
        tags = [];

        for (let l = 0; l < startedRules.length; l++) {
          let rule = startedRules[l];
          stRulesObject[rule.tag] = rule.value;
        }

        for (let tag in rulesObject) {
          tags.push(tag);
        }

        let iN = 0;
        let i = 0;

        function next () {
          i++;

          setTimeout(() => {
            initRule();
          }, 400);
        }

        function initRule () {
          if (i >= startedRules.length) {
            return initAddRule()
          }

          let rule = startedRules[i];

          if (rulesObject[rule.tag]) { // Change rule
            if (rule.value === rulesObject[rule.tag]) { // No need change
              next();
            } else {
              // Need change it. Delete and it and then add
              self.deleteRule(rule.tag).then(() => {
                // Add again

                self.addRule(rule.tag, rulesObject[rule.tag]).then(() => {
                  // Success changed
                  changedRules.push({
                    tag: rule.tag,
                    lastValue: rule.value,
                    newValue: rulesObject[rule.tag]
                  });

                  next();
                }, (err) => {
                  if (callBackError) {
                    callBackError({
                      where: 'add changes',
                      rule: rule,
                      from: 'user_rules',
                      description: 'Occured error in add method when we tried add rule which was changed',
                      error: err
                    });
                  } else {
                    throw err
                  }

                  next();
                });
              }, (err) => {
                if (callBackError) {
                  callBackError({
                    where: 'delete changes',
                    rule: rule,
                    from: 'vk_rules',
                    description: 'Occured error in delete method when we tried delete rule which was changed',
                    error: err
                  });
                } else {
                  throw err
                }

                next();
              });
            }
          } else { // Delete rule
            self.deleteRule(rule.tag).then(() => {
              // Success deleted
              deletedRules.push({
                tag: rule.tag,
                value: rule.value
              });

              next();
            }, (err) => {
              if (callBackError) {
                callBackError({
                  where: 'delete',
                  rule: rule,
                  from: 'vk_rules',
                  description: 'Occured error in delete method when we tried delete rule which not declared in init object',
                  error: err
                });
              } else {
                throw err
              }

              next();
            });
          }
        }

        initRule();

        function nextAdd () {
          iN++;

          setTimeout(() => {
            initAddRule();
          }, 400);
        }

        function initAddRule () {
          if (iN >= tags.length) {
            return resolve({
              changedRules: changedRules,
              addedRules: addedRules,
              deletedRules: deletedRules
            })
          }

          let rule = tags[iN];

          if (!stRulesObject.hasOwnProperty(tags[iN])) { // Need add new rules
            self.addRule(tags[iN], rulesObject[tags[iN]]).then(() => {
              // Success add
              addedRules.push({
                tag: tags[iN],
                value: rulesObject[tags[iN]]
              });

              nextAdd();
            }, (err) => {
              if (callBackError) {
                callBackError({
                  where: 'add',
                  rule: rule,
                  from: 'user_rules',
                  description: 'Occured error in add method when we tried add rule which not declared in vk rules',
                  error: err
                });
              } else {
                throw err
              }

              nextAdd();
            });
          } else {
            nextAdd();
          }
        }
      }, reject);
    })
  }
}

class StreamingAPIConnector {
  constructor (vk) {
    let self = this;
    self._vk = vk;
  }

  async connect (applicationParams = {}) {
    let self = this;

    function initConnect (json = {}) {
      return new Promise((resolve, reject) => {
        StaticMethods.call('streaming.getServerUrl', {
          access_token: json.access_token
        }).then((vkrURL) => {
          let streamingSession, wsc;

          streamingSession = {
            server: vkrURL,
            client: json
          };

          wsc = new WS(`wss://${streamingSession.server.endpoint}/stream?key=${streamingSession.server.key}`, {
            agent: self._vk.agent
          });

          wsc.on('open', () => {
            let _StreamingAPIConnecton =
            new StreamingAPIConnection(self._vk, streamingSession, wsc);

            return resolve(_StreamingAPIConnecton)
          });
        }, reject);
      })
    }
    return new Promise((resolve, reject) => {
      if (applicationParams) {
        if (!StaticMethods.isObject(applicationParams)) {
          return reject(new Error('application params must be an objct parameter'))
        }
      }

      if (!applicationParams.clientId || !applicationParams.clientSecret) {
        if (self._vk && self._vk.__credentials_flow_type) {
          applicationParams.clientId = self._vk.params.client_id;
          applicationParams.clientSecret = self._vk.params.client_secret;
        }
      }

      if (applicationParams.clientId && applicationParams.clientSecret) {
        if (self._vk && self._vk.__credentials_flow_type) {
          initConnect(self._vk.session).then(resolve, reject);
        } else {
          let getParams = {
            client_id: applicationParams.clientId,
            client_secret: applicationParams.clientSecret,
            grant_type: 'client_credentials'
          };

          let url = `${configuration.BASE_OAUTH_URL}access_token?` + qs.stringify(getParams);

          if (this._vk && this._vk.debug) {
            this._vk.debug(EVENT_REQUEST_TYPE, {
              url,
              query: getParams,
              section: 'streamingAPI',
              method: 'GET'
            });
          }

          return fetch$1(url, {
            agent: self._vk.agent
          }).then(async (res) => {
            let vkr = await res.json();

            if (self._vk && self._vk._debugger) {
              try {
                self._vk._debugger.push('response', vkr);
              } catch (e) {
                // Ignore
              }
            }

            if (this._vk && this._vk.debug) {
              this._vk.debug(EVENT_RESPONSE_TYPE, {
                body: vkr,
                section: 'streamingAPI'
              });
            }

            if (vkr) {
              let json = StaticMethods.checkJSONErrors(vkr, reject);

              if (json) {
                initConnect(json).then(resolve, reject);
              } else {
                return reject(new Error("JSON is not valid... oor i don't know"))
              }
            } else {
              return reject(new Error(`Empty response ${vkr}`))
            }
          })
        }
      } else {
        return reject(new Error('clientId and clientSecret not declared'))
      }
    })
  }
}

class Widgets {
  // For call to methods an others, standard procedure
  constructor (vk) {
    let self = this;
    self._vk = vk;
    this.userAgent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36';
  }

  async getLiveViews (videoSourceId = '') {
    let self = this;

    return new Promise((resolve, reject) => {
      if (!videoSourceId || !StaticMethods.isString(videoSourceId)) {
        return reject(self._vk._error('videoSourceId', {
          parameter: 'videoSourceId',
          method: 'widgets.getLiveViews',
          format: 'need format like -2222_22222 (from url)'
        }))
      }

      let headers, alVideoUrl, video, oid, vid, queryParams;

      headers = {
        'origin': 'https://vk.com',
        'referer': `https://vk.com/video?z=video${videoSourceId}`,
        'user-agent': self._vk.params.user_agent || this.userAgent,
        'x-requested-with': 'XMLHttpRequest',
        'content-type': 'application/x-www-form-urlencoded'
      };

      alVideoUrl = `${configuration.PROTOCOL}://${configuration.BASE_DOMAIN}/al_video.php`;
      video = videoSourceId.split('_');
      oid = video[0];
      vid = video[1];

      let form = {
        'act': 'show',
        'al': 1,
        'autoplay': 0,
        'module': 'videocat',
        'video': videoSourceId
      };

      // Get specify hash for get permissions to watch
      queryParams = {
        headers: headers,
        body: qs.stringify(form),
        encoding: 'binary',
        agent: self._vk.agent,
        method: 'POST'
      };

      self._vk.debug(EVENT_REQUEST_TYPE, {
        url: alVideoUrl,
        query: form,
        method: 'POST',
        section: 'widgets'
      });

      return fetch$1(alVideoUrl, queryParams).then(async (res) => {
        res = await res.text();
        res = encoding.convert(res, 'utf-8', 'windows-1251').toString();

        if (self._vk._debugger) {
          try {
            self._vk._debugger.push('response', res);
          } catch (e) {
            // ignore
          }
        }

        self._vk.debug(EVENT_RESPONSE_TYPE, {
          body: res,
          section: 'widgets'
        });

        // Parsing hash from response body {"action_hash" : "hash"}
        let matCH = String(res).match(/("|')action_hash("|')(\s)?:(\s)?('|")(.*?)('|")/i);
        if (matCH) {
          let hash, getVideoViewsQueryParams;

          hash = matCH[0].replace(/([\s':"])/g, '').replace('action_hash', '');

          let url = `${alVideoUrl}?act=live_heartbeat`;

          getVideoViewsQueryParams = {
            body: `al=1&hash=${hash}&oid=${oid}&user_id=0&vid=${vid}`,
            encoding: 'binary', // Special
            headers: headers,
            agent: self._vk.agent,
            method: 'POST'
          };

          // Here is magic
          return fetch$1(url, getVideoViewsQueryParams).then(async (res) => {
            res = await res.text();

            self._vk._debugger.push('response', res);

            try {
              res = JSON.parse(res);
              let countViews = res.payload[1][0];
              if (countViews !== undefined) {
                countViews = parseInt(countViews, 10);
                return resolve(countViews)
              } else {
                return reject(self._vk._error('live_error', {
                  video: video
                }))
              }
            } catch (e) {
              return reject(self._vk._error('live_error', {
                video: video
              }))
            }
          })
        } else {
          return reject(self._vk._error('widgets', {
            video: video
          }, 'live_not_streaming'))
        }
      }).catch(e => reject(e))
    })
  }
}

// LongPollConnection initing automatically by me
class LongPollConnection$1 extends EventEmitter {
  constructor (lpSettings, vk) {
    super();
    let self = this;

    self.config = lpSettings;
    self._vk = vk;
    self.userListeners = {};
    self.closed = false;

    self._middlewaresController = new MiddlewaresMechanism(self);
    self.abortController = new AbortController();

    init();

    async function reconnect () {
      return self._vk.call('groups.getLongPollServer', self.config.userConfig.forGetLongPollServer).then((vkr) => {
        self.config.longpollServer = vkr.server;
        self.config.longpollTs = vkr.ts;
        self.config.longpollKey = vkr.key;

        return init() // reconnect with new parameters
      }).catch((err) => {
        self.emit('reconnectError', new Error(err));
      })
    }

    async function init () {
      let server, forLongPollServer, _w;

      let httpsPref = 'https://';

      if (self.config.longpollServer.substr(0, httpsPref.length) !== httpsPref) {
        self.config.longpollServer = httpsPref + self.config.longpollServer;
      }

      server = `${self.config.longpollServer}`;

      forLongPollServer = {};
      _w = null;

      forLongPollServer.act = 'a_check';
      forLongPollServer.key = self.config.longpollKey;
      forLongPollServer.ts = self.config.longpollTs;
      forLongPollServer.version = self.config.userConfig.forLongPollServer.version;
      forLongPollServer.wait = self.config.userConfig.forLongPollServer.wait;

      if (isNaN(forLongPollServer.version)) {
        forLongPollServer.version = '2';
      }

      _w = forLongPollServer.wait;

      let params = {
        timeout: (_w * 1000) + (1000 * 3),
        headers: {
          'connection': 'keep-alive',
          'content-type': 'application/x-www-form-urlencoded'
        },
        agent: self._vk.agent,
        signal: self.abortController.signal
      };

      server = server + '?' + qs.stringify(forLongPollServer);

      if (self._debug) {
        self._debug({
          type: 'longPollParamsQuery',
          data: params
        });
      }

      self._vk.debug(EVENT_REQUEST_TYPE, {
        url: server,
        query: forLongPollServer,
        method: 'GET',
        section: 'bots.longpoll'
      });

      if (self.closed) return false

      self.lpConnection = fetch$1(server, params).catch((e) => {
        return reconnect()
      }).then(async (res) => {
        if (!res.json) return res
        res = await res.json();

        if (self._vk._debugger) {
          try {
            self._vk._debugger.push('response', res);
          } catch (e) {
            // Ignore
          }
        }

        self._vk.debug(EVENT_RESPONSE_TYPE, {
          body: res,
          section: 'bots.longpoll'
        });

        if (self._debug) {
          self._debug({
            type: 'pollResponse',
            data: res
          });
        }

        let vkr = res;

        if (vkr.ts) {
          self.config.longpollTs = vkr.ts;
        }

        if (vkr.updates) {
          if (vkr.updates.length > 0) {
            self._middlewaresController.run(vkr).then(() => {
              self._checkUpdates(vkr.updates);
            });
          }

          return init()
        }

        if (vkr.failed) {
          if (vkr.failed === 1) { // update ts
            if (vkr.ts) {
              self.config.longpollTs = vkr.ts;
            }

            return init()
          } else if ([2, 3].indexOf(vkr.failed) !== -1) { // need reconnect
            return reconnect()
          } else {
            return self.emit('failure', vkr)
          }
        }

        if (vkr.error) {
          self.emit('error', vkr.error);
          return reconnect()
        }
      }).catch(err => {
        if (err.toString().match('TIMEDOUT') || err.toString().match('ENOENT') || err.toString().match('timeout')) {
          return reconnect()
        }
        return self.emit('error', err)
      });
    }
  }

  async _checkUpdates (updates) {
    let self = this;

    if (Array.isArray(updates)) {
      let len = updates.length;
      for (let updateIndex = 0; updateIndex < len; updateIndex++) {
        let typeEvent = updates[updateIndex].type.toString();
        self.emit(typeEvent, updates[updateIndex].object);
      }
    } else {
      return 'Is not array!'
    }
  }

  /*
   *  This function closes connection and stop it
   *
   *  @return {Promise}
   *  @promise Close connection
   *  @resolve {*} response from abort() method
   *  @rejet {Error} - Eror if connection not inited
   *
   */

  async close () {
    let self = this;

    return new Promise((resolve, reject) => {
      if (self.lpConnection) {
        self.emit('close', {
          time: new Date().getTime()
        });

        self.closed = true;

        return resolve(self.abortController.abort())
      } else {
        return reject(self._vk._error('longpoll_api', {}, 'not_connected'))
      }
    })
  }

  /*
   *  This function enables (adds) your debugger for each query
   *  For example: you can see error if it occured and log it with debugger function
   *
   *  @param {Function|Async Function} [debugg] - Function for debugg all queries
   *  In this function will sending all responses from vk, you can log this object in console for know more
   *
   *  @return {Boolean|Object} - If your function is not a function, then will be returned false,
   *  else LongPollConnection object for chain it
   *
   */

  debug (debugg) {
    let self = this;

    console.warn('[Deprecated method warning] \nThis method will be deprecated in next releases. Please, use new easyvk.Debugger() and set it up in the easyvk configuration like params.debug = myDebugger');

    if (Object.prototype.toString.call(debugg).match(/function/i)) {
      self._debug = debugg;
    } else {
      return false
    }

    return self
  }
}

class LongPollConnector$1 {
  // From EasyVK contructed
  constructor (vk) {
    let self = this; // For the future
    self._vk = vk;
  }

  /*
   *
   *  This function create LongPollConnection and then re-calls to a server for
   *  get new events
   *
   *  @param {Object} [params] - Is your settings for LongPoll connection
   *  @param {Object} [params.forGetLongPollServer] - Is object for firs query
   *  when LongPollConnector getting url for connect. This parameters will be sended with
   *  query uri, and you can see them here https://vk.com/dev/bots_longpoll
   *  @param {Object} [params.forLongPollServer] - Is object with params for each query on longpoll server.
   *  For example: { "wait": 10 } //wait 10seconds for new call
   *
   *  @return {Promise}
   *  @promise Conneto to longpoll server
   *  @resolve {Object} - Is object which contents LongPollConnection
   *  @reject {Error} - vk.com error or just an error from node-fetch module
   *
   */

  async connect (params = {}) {
    let self = this;
    return new Promise((resolve, reject) => {
      if (!StaticMethods.isObject(params)) {
        reject(self._vk._error('is_not_object', {
          'where': 'BotsLongPoll.connect',
          'parameter': 'params'
        }));
      } else {
        if (!StaticMethods.isObject(params.forGetLongPollServer)) {
          params.forGetLongPollServer = {};
        }

        if (params.forLongPollServer) {
          if (!StaticMethods.isObject(params.forLongPollServer)) {
            params.forLongPollServer = {};
          }
        } else {
          params.forLongPollServer = {};
        }

        if (isNaN(params.forGetLongPollServer.lp_version)) {
          params.forGetLongPollServer.lp_version = '2';
        }

        if (isNaN(params.forLongPollServer.wait)) {
          params.forLongPollServer.wait = '25';
        }

        if (!params.forGetLongPollServer.group_id && self._vk.session.group_id) {
          params.forGetLongPollServer.group_id = self._vk.session.group_id;
        }

        self._vk.call('groups.getLongPollServer', params.forGetLongPollServer).then((vkr) => {
          let forLongPoll = {
            longpollServer: vkr.server,
            longpollTs: vkr.ts,
            longpollKey: vkr.key,
            responseGetServer: vkr,
            userConfig: params
          };

          return resolve(new LongPollConnection$1(forLongPoll, self._vk))
        }, reject);
      }
    })
  }
}

class EasyVKSession {
  constructor (vk, dataSession = {}) {
    let self = this;

    let _props = {
      session: dataSession,
      path: vk.params.session_file,
      vk: vk
    };

    let canChanged = ['path'];

    for (let prop in _props) {
      let settings = {
        value: _props[prop]
      };

      if (canChanged.indexOf(prop) !== -1) {
        settings.configurable = true;
      }

      Object.defineProperty(self, prop, settings);
    }

    // Use session data with methods
    for (let prop in self.session) {
      Object.defineProperty(self, prop, {
        enumerable: true,
        configurable: true,
        value: self.session[prop]
      });
    }

    return self
  }

  /*
   *  This method saved your session in file
   *
   *  @return {Promise}
   *
   */

  async save () {
    let self = this;

    return new Promise((resolve, reject) => {
      let s;

      if (!(self.path)) {
        return reject(self.vk._error('session', {}, 'need_path'))
      }

      s = JSON.stringify(self);

      let buf = Buffer.from(s, 'utf8');

      fs.writeFile(self.path, buf, (err) => {
        if (err) {
          return reject(new Error(err))
        }

        return resolve({
          vk: self.vk
        })
      });
    })
  }

  /*
   *  This function saves your session and cleaning it, make this empty
   *  @returns Promise
   *
   */

  async clear () {
    let self = this;

    return new Promise((resolve, reject) => {
      for (let prop in self) {
        Object.defineProperty(self, prop, {
          value: undefined,
          enumerable: true,
          configurable: true
        });
      }

      self.save().then(resolve, reject);
    })
  }

  /*
   *  This function set up your path session, ou can change sesson path
   *
   *  @param {String} path is absolute path for you file
   *  @return {Promise}
   *
   */

  async setPath (path = '') {
    let self = this;

    return new Promise((resolve, reject) => {
      fs.writeFile(path, '', (err) => {
        if (err) {
          return reject(err)
        }

        Object.defineProperty(self, 'path', {
          configurable: true,
          value: path
        });

        // Update for easyvk functions, for latest releases
        self.vk.params.session_file = path;

        return resolve({
          vk: self.vk
        })
      });
    })
  }
}

/**
 *   In this file are http widgets for EasyVK
 *   You can use it
 *
 *   Author: @ciricc
 *   License: MIT
 *
 */

let fetch = null;

class HTTPEasyVKClient {
  constructor ({ _jar, vk, httpVk, config, parser }) {
    this._config = config;

    this.headersRequest = {
      'User-Agent': this._config.userAgent,
      'content-type': 'application/x-www-form-urlencoded'
    };

    this.LOGIN_ERROR = 'Need login by form, use .loginByForm() method';

    this._vk = vk;
    this._authjar = _jar;
    this._parser = parser;

    Object.defineProperty(this, 'audio', {
      get: () => {
        console.warn('[Deprecated] Audio API is fully deprecated!');
      }
    });
  }

  async readStories (vkId = 0, storyId = 0) {
    let self = this;

    storyId = Number(storyId);
    if (isNaN(storyId)) storyId = 0;

    return new Promise((resolve, reject) => {
      vkId = Number(vkId);

      if (isNaN(vkId)) return reject(new Error('Is not numeric vk_id'))

      // else try get sttories from user
      if (!self._authjar) return reject(new Error(self.LOGIN_ERROR))

      let url = `${configuration.PROTOCOL}://m.${configuration.BASE_DOMAIN}/fv?to=/id${vkId}?_fm=profile&_fm2=1`;

      self._vk.debug(EVENT_REQUEST_TYPE, {
        url,
        query: `to=/id${vkId}?_fm=profile&_fm2=1`,
        method: 'GET',
        section: 'httpClient'
      });

      return fetch(url, {
        method: 'GET',
        headers: self.headersRequest,
        agent: self._vk.agent
      }).then(async (res) => {
        res = await res.text();

        self._vk.debug(EVENT_RESPONSE_TYPE, {
          body: res,
          section: 'httpClient'
        });

        let stories = self.__getStories(res, 'profile');
        let i = 0;

        stories.forEach((story) => {
          if (Array.isArray(story.items)) {
            story.items.forEach(item => {
              self._story_read_hash = story.read_hash;

              if (storyId) {
                // Only one story
                if (item.raw_id === storyId) {
                  self.__readStory(story.read_hash, item.raw_id, 'profile');
                }
              } else {
                // All stories
                self.__readStory(story.read_hash, item.raw_id, 'profile');
              }

              i++;
            });
          }
        });

        return resolve(i)
      }).catch(err => reject(err))
    })
  }

  __getStories (response = '', type = 'feed') {
    response = String(response);

    let storiesMatch, superStories;

    if (type === 'feed') {
      storiesMatch = /cur\['stories_list_feed'\]=\[(.*?)\];/;
      superStories = /cur\['stories_list_feed'\]=/;
    } else {
      storiesMatch = /cur\['stories_list_profile'\]=\[(.*?)\];/;
      superStories = /cur\['stories_list_profile'\]=/;
    }

    let stories = response.match(storiesMatch);

    if (!stories || !stories[0]) return []

    try {
      stories = JSON.parse(
        String(stories[0])
          .replace(superStories, '')
          .replace(/;/g, '')
      );
    } catch (e) {
      stories = [];
    }

    return stories
  }

  async __readStory (read_hash = '', stories = '', source = 'feed', cb) {
    let self = this;

    let url = 'al_stories.php';

    let form = {
      act: 'read_stories',
      'al': '1',
      'all': 1,
      'connection_type': 'wi-fi',
      'hash': read_hash,
      'source': source,
      'progress': 0,
      'story_id': stories,
      XML: true
    };

    self._vk.debug(EVENT_REQUEST_TYPE, {
      url,
      query: form,
      method: 'POST',
      section: 'httpClient'
    });

    return this.post(url, form).then((res) => {
      return (cb) ? cb(null, res, null) : true
    })
  }

  async readFeedStories () {
    let self = this;

    return new Promise((resolve, reject) => {
      // else try get sttories from user
      if (!self._authjar) return reject(new Error(self.LOGIN_ERROR))

      let url = `${configuration.PROTOCOL}://m.${configuration.BASE_DOMAIN}/fv?to=%2Ffeed%3F_fm%3Dfeed%26_fm2%3D1`;

      self._vk.debug(EVENT_REQUEST_TYPE, {
        url,
        query: `to=%2Ffeed%3F_fm%3Dfeed%26_fm2%3D1`,
        method: 'GET',
        section: 'httpClient'
      });

      return fetch(url, {
        headers: self.headersRequest,
        agent: self._vk.agent
      }).then(async (res) => {
        res = await res.text();

        self._vk.debug(EVENT_RESPONSE_TYPE, {
          body: res,
          section: 'httpClient'
        });

        // parse stories
        let stories = self.__getStories(res, 'feed');

        let i = 0;

        stories.forEach((story) => {
          if (Array.isArray(story.items)) {
            story.items.forEach(item => {
              self.__readStory(story.read_hash, item.raw_id, 'feed');
              i++;
            });
          }
        });

        return resolve(i)
      }).catch(err => reject(err))
    })
  }

  async post (file, form, isMobile) {
    return this.request(file, form, true, isMobile)
  }

  async get (file, form, isMobile) {
    return this.request(file, form, false, isMobile)
  }

  async request (file, form = {}, post = true, isMobile = false) {
    let self = this;
    return new Promise((resolve, reject) => {
      let mobile = '';

      if (isMobile) {
        mobile = 'm.';
      }

      let method = 'post';

      if (post !== true) method = 'get';

      let headers = {
        'user-agent': self._config.userAgent
      };

      if ((isMobile && method === 'post') || form.XML === true) {
        headers['x-requested-with'] = 'XMLHttpRequest';
      }

      headers['content-type'] = 'application/x-www-form-urlencoded';

      let url = `${configuration.PROTOCOL}://${mobile}${configuration.BASE_DOMAIN}/` + file;

      self._vk._debugger.push('request', {
        url,
        method,
        headers,
        form
      });

      self._vk.debug(EVENT_REQUEST_TYPE, {
        url,
        query: form,
        section: 'httpClient',
        method: method
      });

      return fetch(url, {
        agent: self._vk.agent,
        headers,
        method,
        body: method === 'get' ? undefined : qs.stringify(form),
        qs: method === 'get' ? form : undefined
      }).then(async (res) => {
        res = await res.textConverted();

        self._vk._debugger.push('response', res);

        self._vk.debug(EVENT_RESPONSE_TYPE, {
          url: url,
          query: form,
          section: 'httpClient',
          body: res
        });

        if (!res.length) {
          return reject(self._vk._error('http_client', {}, 'not_have_access'))
        }

        if (form.utf8) {
          res = encoding.convert(res, 'utf-8', 'windows-1251').toString();
        }

        if (form.retOnlyBody) return resolve(res)

        let json = self._parseResponse(res);
        if (json.payload && String(json.payload[0]) === '3') {
          await this.request('',
            {
              _origin: 'https://vk.com', // Only known in Desktop methods
              ip_h: JSON.parse(json.payload[1][0]),
              role: 'al_frame',
              to: JSON.parse(json.payload[1][1])
            }
          );
          return resolve(await this.request(...arguments))
        }

        if (form.autoParse) {
          return resolve(VKResponseReturner(StaticMethods, {
            response: json
          }))
        }

        return resolve(json)
      }).catch(err => reject(err))
    })
  }

  async goDesktop () {
    return this.request('fv?to=/mail?_fm=mail&_fm2=1', {}, false, true)
  }

  async goMobile () {
    return this.request('mail?act=show&peer=0&_ff=1', {}, false, true)
  }

  async requestMobile (...args) {
    return this.request(...args, true)
  }

  _parseResponse (e) {
    return this._parser(e)
  }

  _parseJSON (body, reject) {
    let self = this;

    let json = self._parseResponse(body.split('<!>'));

    if (typeof json[6] === 'object') json[5] = json[6];

    if (typeof json[5] === 'string') {
      return reject(new Error(json[5]))
    }

    if (!json[5]) {
      return reject(self._vk._error('http_client', {}, 'not_have_access'))
    }

    json = json[5];

    return json
  }
}

class HTTPEasyVK {
  constructor (vk) {
    let self = this;

    self.headersRequest = {
      'content-type': 'application/x-www-form-urlencoded'
    };

    self._vk = vk;
  }

  async __checkHttpParams (params = {}) {
    return new Promise((resolve, reject) => {
      if (!params.userAgent) {
        params.userAgent = configuration['HTTP_CLIENT']['USER_AGENT'];
      }

      params.userAgent = String(params.userAgent);

      if (!params.cookies) {
        params.cookies = configuration['HTTP_CLIENT']['COOKIE_PATH'];
      }

      params.cookies = String(params.cookies);

      return resolve(params)
    })
  }

  _parseResponse (e, json = true) {
    if (e) {
      e = String(e).replace('<!--', '');
      if (json) {
        try {
          e = JSON.parse(e);
        } catch (_e) {
          return e
        }
      }
    }
    return e
  }

  async loginByForm (params = {}) {
    let self = this;

    return new Promise((resolve, reject) => {
      let pass = params.password || self._vk.params.password;
      let login = params.username || self._vk.params.username;

      let captchaSid = params.captchaSid || self._vk.params.captcha_sid;
      let captchaKey = params.captchaKey || self._vk.params.captcha_key;

      let code = params.code;

      const captchaHandler = params.captchaHandler || self._vk.params.captchaHandler;

      if (!pass || !login) return reject(self._vk._error('http_client', {}, 'need_auth'))

      self.__checkHttpParams(params).then((p) => {
        let params = p;
        self._config = params;

        self.headersRequest['User-Agent'] = self._config.userAgent;

        let cookiepath = self._config.cookies;

        if (!self._vk.params.reauth && !params.reauth) {
          let data;

          if (!fs.existsSync(cookiepath)) {
            fs.closeSync(fs.openSync(cookiepath, 'w'));
          }

          data = fs.readFileSync(cookiepath).toString();

          try {
            data = JSON.parse(data);
          } catch (e) {
            data = null;
          }

          if (data) {
            let jar = new toughCookie.CookieJar(new CookieStore(cookiepath));

            self._authjar = jar;
            fetch = getJarFetch(self._authjar);

            return createClient(resolve)
          }
        }

        let vHttp = self._vk;

        let jar = new toughCookie.CookieJar(new CookieStore(cookiepath));

        self._authjar = jar;
        fetch = getJarFetch(self._authjar);

        if (!self._vk.params.reauth && !params.reauth) {
          if (Object.keys(jar).length) {
            return actCheckLogin().then(() => {
              return createClient(resolve, vHttp)
            }, (r) => {
              return goLogin()
            })
          }
        }

        fs.writeFileSync(cookiepath, '{}');
        jar = new toughCookie.CookieJar(new CookieStore(cookiepath));
        self._authjar = jar;

        fetch = getJarFetch(self._authjar);

        return goLogin()

        async function goLogin () {
          let url = `${configuration.PROTOCOL}://${configuration.MOBILE_SUBDOMAIN}.${configuration.BASE_DOMAIN}/`;

          self._vk.debug(EVENT_REQUEST_TYPE, {
            url,
            query: ``,
            method: 'GET',
            section: 'httpClient'
          });

          fetch(url, {
            method: 'GET',
            headers: {
              ...self.headersRequest
            },
            agent: self._vk.agent,
            cache: 'no-cache'
          }).then(async (res) => {
            res = await res.text();

            self._vk.debug(EVENT_RESPONSE_TYPE, {
              body: res,
              section: 'httpClient'
            });

            let body = res;

            self._vk._debugger.push('response', res);

            let matches = body.match(/action="(.*?)"/);

            if (!matches || !body.match(/password/)) { // Если пользовтаель уже авторизован по кукисам, чекаем сессию
              return actCheckLogin().then(() => {
                return createClient(resolve, vHttp)
              }, reject)
            }

            let POSTLoginFormUrl = matches[1];

            if (!POSTLoginFormUrl.match(/login\.vk\.com/)) return reject(self._vk._error('http_client', {}, 'not_supported'))
            return actLogin(POSTLoginFormUrl).then(resolve, reject)
          }).catch(err => reject(err));
        }

        async function actCheckLogin (jar) {
          return new Promise((resolve, reject) => {
            let url = `${configuration.PROTOCOL}://${configuration.BASE_DOMAIN}/al_im.php`;

            let form = {
              act: 'a_dialogs_preload',
              al: 1,
              gid: 0,
              im_v: 3,
              rs: ''
            };

            self._vk.debug(EVENT_REQUEST_TYPE, {
              url,
              query: form,
              method: 'POST',
              section: 'httpClient'
            });

            return fetch(url, {
              method: 'POST',
              url,
              body: qs.stringify(form),
              agent: self._vk.agent,
              headers: {
                ...self.headersRequest,
                'x-requested-with': 'XMLHttpRequest'
              }
            }).then(async (res) => {
              res = await res.text();

              self._vk.debug(EVENT_RESPONSE_TYPE, {
                body: res,
                section: 'httpClient'
              });

              self._vk._debugger.push('response', res);
              res = self._parseResponse(res);
              if (Number(res.payload[0]) === 0) {
                return resolve(true)
              } else {
                return reject(new Error('Need update session'))
              }
            }).catch(err => reject(err))
          })
        }

        function _catchCaptcha (params = {}) {
          let { err, reCall, _needSolve, _rejecterReCall, data, reject } = params;

          let vkr = err;

          if (_needSolve) {
            try {
              _rejecterReCall({
                error: false,
                reCall: () => {
                  return reCall()
                }
              });
            } catch (e) { reject(e); }

            return
          }

          const captchaSid = vkr.captcha_sid;
          const captchaImg = vkr.captcha_img;

          let paramsForHandler = {
            captcha_sid: captchaSid,
            captcha_img: captchaImg,
            params: data,
            vk: self._vk
          };

          paramsForHandler.resolve = (captchaKey) => {
            return new Promise((resolve, reject) => {
              data.captcha_key = captchaKey;
              data.captcha_sid = captchaSid;

              try {
                reCall('NEED SOLVE', resolve, reject, data);
              } catch (errorRecall) { /* Need pass it */ }
            })
          };

          captchaHandler(paramsForHandler);
        }

        async function actLogin (loginURL) {
          return new Promise((resolve, reject) => {
            async function makeAuth (_needSolve, _resolverReCall, _rejecterReCall, getData) {
              return fetch(loginURL, {
                method: 'POST',
                agent: self._vk.agent,
                body: qs.stringify(getData),
                headers: {
                  ...self.headersRequest
                }
              }).then(async res => {
                let body = await res.text();
                if (body.match(/authcheck_code/)) {
                  if (code) {
                    let checkCodeURL = body.match(/action([\s]+)?=([\s]+)?("|')(\/login\?act=authcheck_code([^"']+))/);

                    checkCodeURL = checkCodeURL ? checkCodeURL[4] : null;

                    if (!checkCodeURL) return reject(new Error('Not found authcheck url'))
                    checkCodeURL = `${configuration.PROTOCOL}://${configuration.MOBILE_SUBDOMAIN}.${configuration.BASE_DOMAIN}${checkCodeURL}`;

                    let checkCodeData = {
                      _ajax: 1,
                      code,
                      remember: 1
                    };

                    let res = await fetch(checkCodeURL, {
                      method: 'POST',
                      agent: self._vk.agent,
                      body: qs.stringify(checkCodeData),
                      headers: {
                        ...self.headersRequest
                      }
                    });

                    res = await res.text();

                    if (res.match(/authcheck_code/)) {
                      let err = new Error('Wrong code');
                      err.is2fa = true;
                      err.isWrong = true;
                      return reject(err)
                    }
                  } else {
                    let err = new Error('You need input two factor code');
                    err.is2fa = true;
                    return reject(err)
                  }
                }

                if (body.match(/captcha/)) {
                  let captchaUrl = body.match(/\/captcha.php([^"]+)/);
                  if (captchaUrl) {
                    captchaUrl = `${configuration.PROTOCOL}://${configuration.BASE_DOMAIN}${captchaUrl[0]}`;
                    let captchaSid = captchaUrl.match(/sid=([0-9]+)/);
                    if (captchaSid) {
                      captchaSid = Number(captchaSid[1]);

                      let err = {
                        captcha_sid: captchaSid,
                        captcha_img: captchaUrl
                      };

                      return _catchCaptcha({
                        err,
                        reCall: () => {
                          return makeAuth(0, 0, 0, getData)
                        },
                        _needSolve,
                        _resolverReCall,
                        _rejecterReCall,
                        data: getData,
                        reject
                      })
                    } else {
                      return reject(new Error('You have captcha error, but http client dont recognize where is captcha_sid parameter'))
                    }
                  } else {
                    return reject(new Error('You have captcha error, but http client dont recognize where'))
                  }
                }
                self._vk.debug(EVENT_RESPONSE_TYPE, {
                  body: body,
                  section: 'httpClient'
                });

                self._vk._debugger.push('response', body);
                if (_needSolve) {
                  return _resolverReCall(createClient(resolve, vHttp))
                } else {
                  return createClient(resolve, vHttp)
                }
              }).catch(err => reject(err))
            }

            return makeAuth(0, 0, 0, {
              email: login,
              pass,
              captcha_sid: captchaSid,
              captcha_key: captchaKey
            })
          })
        }

        async function createClient (r, vHttp) {
          let HTTPClient = new HTTPEasyVKClient({
            _jar: self._authjar,
            vk: self._vk,
            httpVk: vHttp,
            config: self._config,
            parser: self._parseResponse
          });

          await HTTPClient.goDesktop();

          return r(HTTPClient)
        }
      }, reject);
    })
  }
}

function getJarFetch (jar) {
  return async function myFetch (url, options) {
    if (!options) options = {};
    if (!options.headers) options.headers = {};
    if (!options.jar) options.jar = jar;

    if (options.jar) {
      let cookies = options.jar.getCookiesSync(url).join('; ');
      options.headers = {
        ...options.headers,
        cookie: cookies
      };
    }

    const opts = Object.assign({}, options, { redirect: 'manual' });

    let res = await fetch$1(url, opts);

    return new Promise(async (resolve, reject) => {
      if (res.headers.raw()['set-cookie']) { // Set cookies like browser
        let cookies = res.headers.raw()['set-cookie'].map(toughCookie.Cookie.parse);
        let promises = [];

        if (options.jar) {
          cookies.forEach(cookie => {
            promises.push(new Promise((resolve, reject) => {
              options.jar.setCookie(cookie, res.url, () => {
                return resolve(true)
              });
            }));
          });
        }

        await Promise.all(promises);
      }

      const isRedirect = (res.status === 303 || ((res.status === 301 || res.status === 302 || res.status === 307)));

      if (isRedirect) {
        const optsForGet = Object.assign({}, {
          method: res.status === 307 ? options.method : 'GET',
          body: res.status === 307 ? options.body : null,
          follow: options.follow !== undefined ? options.follow - 1 : undefined,
          agent: options.agent,
          headers: options.headers
        });

        return myFetch(res.headers.get('location'), optsForGet)
          .then((res) => {
            return resolve(res)
          })
      }

      return resolve(res)
    })
  }
}

var errors = {
  'session_not_valid': {
    'code': 1,
    'description': 'JSON in session file is not valid',
    'ru_description': 'JSON файла сессии не имеет правильный формат'
  },
  'session_not_found': {
    'code': 2,
    'description': 'Session file is not found',
    'ru_description': 'Файл сессии не найден'
  },
  'empty_session': {
    'code': 3,
    'description': 'Session file is empty',
    'ru_description': 'Файл сессии пустой'
  },
  'empty_response': {
    'code': 4,
    'description': 'The server responsed us with empty data',
    'ru_description': 'Ответ сервера пришел пустым'
  },
  'access_token_not_valid': {
    'code': 5,
    'description': 'Access token not valid',
    'ru_description': 'Access токен не правильный'
  },
  'captcha_error': {
    'code': 6,
    'description': 'You need solve it and then put to params captcha_key, or use captchaHandler for solve it automatic',
    'ru_description': 'Необходимо решить капчу, вставьте в параметр captcha_key код с картинки или используйте captchaHandler для того, чтобы решать капчу автоматически'
  },
  'method_deprecated': {
    'code': 7,
    'description': 'This method was deprecated',
    'ru_description': 'Этот метод был удален'
  },
  'is_not_string': {
    'code': 8,
    'description': 'This parameter is not string',
    'ru_description': 'Параметр должен быть строкой'
  },
  'live_error': {
    'code': 10,
    'description': "Maybe VK algo was changed, but we can't parse count of views from this video",
    'ru_description': 'Может быть, алгоритмы ВКонтакте были изменены, но сейчас мы не можем получить количество просмотров этой странсляции'
  },
  'server_error': {
    'code': 11,
    'description': "Server was down or we don't know what happaned",
    'ru_description': 'Сервер упал, или нам неизвестно, что произошло'
  },
  'invalid_response': {
    'code': 12,
    'description': 'Server responsed us with not a JSON format',
    'ru_description': 'Сервер ответил не в формате JSON'
  },
  'is_not_object': {
    'code': 13,
    'description': 'This parameter is not an object',
    'ru_description': 'Параметр должен быть объектом'
  },
  'upload_url_error': {
    'code': 14,
    'description': 'upload_url is not defied in vk response',
    'ru_description': 'upload_url не указан в ответе сервера'
  },
  'is_not_function': {
    'code': 15,
    'description': 'This parameter is not a function',
    'ru_description': 'Параметр должен быть функцией'
  },
  'is_not_number': {
    'code': 16,
    'description': 'This parameter is not a number',
    'ru_description': 'Параметр должен быть числом'
  },
  'http_client': {
    'parent_hash': 2000,
    'errors': {
      'need_auth': {
        'code': 1,
        'description': 'Need authenticate by password and username. This data not saving in session file!',
        'ru_description': 'Вам нужно ввести параметр username и password, в сессии не сохранен пароль и логин'
      },
      'not_supported': {
        'code': 2,
        'description': 'Library does not support this authentication way... sorry',
        'ru_description': 'Библиотека не поддерживает авторизацию через HTTP... к сожалению'
      }
    }
  },
  'longpoll_api': {
    'parent_hash': 3000,
    'errors': {
      'not_connected': {
        'code': 1,
        'description': 'LongPoll not connected to the server',
        'ru_description': 'LongPoll не подключен к серверу'
      },
      'event_already_have': {
        'code': 2,
        'description': 'This eventCode is already have listening',
        'ru_description': 'Этот eventCode уже прослушивается'
      }
    }
  },
  'session': {
    'parent_hash': 3100,
    'errors': {
      'need_path': {
        'code': 1,
        'description': 'You need set path for session file',
        'ru_description': 'Вам нужно установить путь к файлу сессии'
      }
    }
  },
  'widgets': {
    'parent_hash': 4000,
    'errors': {
      'live_not_streaming': {
        'code': 1,
        'description': 'The live video is not streaming now',
        'ru_description': 'Live трансляция в данный момент не проводится'
      }
    }
  }
};

class EasyVKError extends Error {
  constructor (error, name = '', data = {}) {
    super(error.description);

    let self = this;

    self.error_code = error.code;
    self.easyvk_error = true;
    self.data = data;
    self.name = name;
  }
}

class EasyVKErrors {
  constructor () {
    let self = this;
    self._errors = errors;
  }

  error (name = '', data = {}, parent = '') {
    let self = this;

    name = String(name);
    parent = String(parent);

    if (self._errors[name]) {
      let err;

      if (self._errors[name]['errors'] && self._errors[name]['errors'][parent]) {
        err = self._errors[name]['errors'][parent];
        err.code += (self._errors[name]['parent_hash'] || -100000);
      } else {
        err = self._errors[name];
      }

      if (err[self._lang + '_description']) {
        err.description = err[self._lang + '_description'];
      }

      let stringId = name;

      if (self._errors[name]['errors'] && self._errors[name]['errors'][parent]) {
        stringId = name + '\\' + parent;
      }

      return new EasyVKError(err, stringId, data)
    }

    let notHaveError = 'Not have this error in EasyVKErrors object!';

    if (self._lang === 'ru') {
      notHaveError = 'Данная ошибка не описана в объекте EasyVKErrors';
    }

    return new Error(notHaveError)
  }

  setLang (lang = 'ru') {
    let self = this;

    self._lang = String(lang);
  }
}

var easyVKErrors = new EasyVKErrors();

const authTypes = ['user', 'group', 'app'];

/**
 *  EasyVK module. In this module creates session by your params
 *  And then you will get a EasyVK object (Session creates in the easyvkInit.js file)
 *  Author: @ciricc
 *  License: MIT
 *
 */

class EasyVK {
  // Here will be created session
  constructor (params, debuggerRun) {
    this.params = params;

    this._debugger = new RequestsDebugger(this);

    this.debug = (t, d) => {
      if (this.params.debug) {
        this.params.debug.emit(t, d);
      }
    };

    this.debuggerRun = debuggerRun || this._debugger;

    this._errors = easyVKErrors;

    this._errors.setLang(params.lang);
  }

  get debugger () {
    console.warn('[Deprecated property warning] \nvk.debugger property will be deprecated in next releases. Please, use new easyvk.Debugger() and set it up in the easyvk configuration like params.debug = myDebugger');
    return this._debugger
  }

  set debugger (d) {
    this._debugger = d;
  }

  async _init () {
    let self = this;
    return new Promise((resolve, reject) => {
      let session, params;

      session = {};
      params = self.params;

      if (params.proxy) {
        let options = new url.URL(params.proxy);
        let opts = {};

        for (let i in options) {
          opts[i] = options[i];
        }

        options = opts;

        if (options.username || options.password) {
          options.auth = options.username + ':' + options.password;
        }

        options.keepAlive = true;
        options.keepAliveMsecs = 30000;

        self.agent = new ProxyAgent(options, true);
      } else {
        self.agent = new https.Agent({
          keepAlive: true,
          keepAliveMsecs: 30000
        });
      }

      let defaultDataParams = {
        client_id: params.client_id || configuration.WINDOWS_CLIENT_ID,
        client_secret: params.client_secret || configuration.WINDOWS_CLIENT_SECRET,
        v: params.api_v,
        lang: params.lang === 'undefined' ? 'ru' : params.lang,
        '2fa_supported': 1
      };

      if (params.captcha_key) {
        defaultDataParams.captcha_sid = params.captcha_sid;
        defaultDataParams.captcha_key = params.captcha_key;
      }

      if (params.code && params.code.toString().length !== 0) {
        defaultDataParams.code = params.code;
      }

      if (!params.captchaHandler || !Object.prototype.toString.call(params.captchaHandler).match(/Function/)) {
        params.captchaHandler = (thread) => {
          throw self._error('captcha_error', {
            captcha_key: thread.captcha_key,
            captcha_sid: thread.captcha_sid,
            captcha_img: thread.captcha_img
          })
        };
      }

      self.captchaHandler = params.captchaHandler;

      /* if user wants to get data from file, need get data from file
         or generate this file automatically with new data */

      if (!params.reauth) {
        let data;

        try {
          data = fs.readFileSync(params.session_file);
        } catch (e) {
          data = false;
        }

        if (data) {
          try {
            data = JSON.parse(data.toString());

            if (
              (data.access_token && data.access_token === params.access_token) || // If config token is session token
              (params.username && params.username === data.username) ||
              (params.client_id && params.client_id === data.client_id && !params.access_token && !params.username)// or if login given, it need be same
            ) {
              if (data.access_token) {
                session = new EasyVKSession(self, data);
                self.session = session;
                return initResolve(self)
              } else {
                if (!(params.username && params.password) && !params.access_token && !params.client_id && params.client_secret) {
                  return reject(self._error('empty_session'))
                }
              }
            }
          } catch (e) {
            if (!(params.username && params.password) && !params.access_token) {
              return reject(self._error('session_not_valid'))
            }
          }
        } else {
          if (!(params.username && params.password) && !params.access_token && !(params.client_id && params.client_secret)) {
            return reject(self._error('empty_session'))
          }
        }
      }

      if (!session.access_token) { // If session file contents access_token, try auth with it
        if (params.access_token) {
          session.access_token = params.access_token;
          if (self.params.authType) {
            let { authType } = self.params;
            if (authType === authTypes[0]) {
              initToken();
            } else if (authType === authTypes[1]) {
              groupToken();
            } else if (authType === authTypes[2]) {
              appToken();
            } else {
              initToken();
            }
          } else {
            initToken();
          }
        } else if (params.username) {
          // Try get access_token with auth
          let getData = {
            username: params.username,
            password: params.password,
            grant_type: 'password',
            device_id: '',
            libverify_support: 1,
            scope: 'all',
            v: '5.122'
          };

          makeAuth(0, 0, 0, getData); // пиздец
        } else if (params.client_id) {
          let getData = {
            grant_type: 'client_credentials'
          };

          getData = prepareRequest(getData);

          let url = configuration.BASE_OAUTH_URL + 'token/?' + getData;

          let headers = {
            'User-Agent': params.userAgent
          };

          self.debug(EVENT_REQUEST_TYPE, {
            url: url,
            query: getData,
            method: 'GET'
          });

          fetch$1(url, {
            agent: self.agent,
            headers: headers
          }).then(async (res) => {
            self.debug(EVENT_RESPONSE_TYPE, { body: res });

            if (self.debuggerRun) {
              try {
                self.debuggerRun.push('response', res);
              } catch (e) {
                return reject(new Error('DebuggerRun is not setuped correctly'))
              }
            }

            res = await res.json();
            return completeSession(null, res, {
              credentials_flow: 1,
              client_id: params.client_id
            }).catch((e) => {
              if (params.onlyInstance) {
                self.session = new EasyVKSession(self, {
                  access_token: null
                });

                initResolve(self);
              } else {
                return reject(e)
              }
            })
          }).catch(e => reject(e));
        }
      }

      async function makeAuth (_needSolve, _resolverReCall, _rejecterReCall, getData) {
        let queryData = prepareRequest(getData);
        let url = configuration.BASE_OAUTH_URL + 'token/?' + queryData;

        self.debug(EVENT_REQUEST_TYPE, {
          url: url,
          query: queryData,
          method: 'GET'
        });

        return fetch$1(url, {
          headers: {
            'User-Agent': params.userAgent,
            'X-VK-Android-Client': 'new'
          },
          agent: self.agent
        }).then(async res => {
          self.debug(EVENT_RESPONSE_TYPE, { body: res });

          if (self.debuggerRun) {
            try {
              self.debuggerRun.push('response', res);
            } catch (e) {
              return reject(new Error('DebuggerRun is not setuped correctly'))
            }
          }

          res = await res.json();

          return completeSession(null, res, {
            user_id: null
          }).catch(async (err) => {
            try {
              self._catchCaptcha({ err,
                reCall: () => {
                  return makeAuth(0, 0, 0, getData)
                },
                _needSolve,
                _resolverReCall,
                _rejecterReCall,
                data: getData,
                reject });
            } catch (e) {
              if (err.validation_sid && err.validation_type && String(err.validation_type).match('sms')) {
                let validatePhoneData = {
                  https: 1,
                  lang: defaultDataParams.lang,
                  v: defaultDataParams.v,
                  client_id: defaultDataParams.client_id,
                  client_secret: defaultDataParams.client_secret,
                  api_id: 2274003,
                  libverify_support: 1,
                  sid: err.validation_sid
                };
                await StaticMethods.call('auth.validatePhone', validatePhoneData, 'post', self.debuggerRun, self.agent)
                  .catch(e => {});
              }

              reject(err);
            }
          })
        }).catch(e => reject(e))
      }

      function completeSession (err, res, object = {}) {
        return new Promise((resolve, reject) => {
          let vkr = prepareResponse(err, res);
          let json = generateSessionFromResponse(vkr, reject);

          if (json) {
            session = json;
            Object.assign(session, object);
            initToken();
            resolve(true);
          } else {
            return reject(self._error('empty_response', {
              response: vkr
            }))
          }
        })
      }

      function generateSessionFromResponse (vkr, rej) {
        let json = StaticMethods.checkJSONErrors(vkr, rej || reject);

        if (json) {
          json = JSON.parse(JSON.stringify(json));
          json.user_id = null;

          return json
        }
      }

      function prepareRequest (getDataObject = {}) {
        let Obj = Object.assign(getDataObject, defaultDataParams);

        Obj = StaticMethods.urlencode(Obj);

        if (self.debuggerRun) {
          try {
            self.debuggerRun.push('request', configuration.BASE_OAUTH_URL + 'token/?' + Obj);
          } catch (e) {
            return reject(new Error('DebuggerRun is not setuped correctly'))
          }
        }

        return Obj
      }

      function prepareResponse (err, res) {
        if (err) {
          return reject(new Error(err))
        }

        return res
      }

      function initToken () {
        if (!session.user_id && !session.group_id) {
          let token, getData;

          token = session.access_token || params.access_token;

          if (session.credentials_flow) {
            self.__credentials_flow_type = true;
            self.session = session;

            initResolve(self);
          } else {
            getData = {
              access_token: token,
              v: params.api_v,
              fields: params.fields.join(',')
            };

            let url = configuration.BASE_CALL_URL + 'users.get?' + prepareRequest(getData);

            self.debug(EVENT_REQUEST_TYPE, {
              url: url + '?' + StaticMethods.urlencode(getData),
              query: getData,
              method: 'GET'
            });

            return fetch$1(url, {
              agent: self.agent,
              headers: {
                'User-agent': params.userAgent
              }
            }).then(async (res) => {
              self.debug(EVENT_RESPONSE_TYPE, { body: res });

              if (self.debuggerRun) {
                try {
                  self.debuggerRun.push('response', res);
                } catch (e) {
                  return reject(new Error('DebuggerRun is not setuped correctly'))
                }
              }

              res = await res.json();

              let vkr = prepareResponse(null, res);

              if (vkr) {
                let json = StaticMethods.checkJSONErrors(vkr, reject);
                if (json) {
                  if (Array.isArray(json) && json.length === 0) {
                    session = {
                      access_token: session.access_token
                    };

                    if (self.params.authType && self.params.authType === authTypes[0]) {
                      return reject(new Error('Is not a user token! Or this token is not valid (expired)'))
                    } else {
                      appToken();
                    }
                  } else {
                    session = {
                      access_token: session.access_token
                    };

                    session.user_id = json[0].id;
                    session.first_name = json[0].first_name;
                    session.last_name = json[0].last_name;
                    session.username = params.username;

                    for (let i = 0; i < params.fields.length; i++) {
                      if (json[0][params.fields[i]] && session[params.fields[i]] === undefined) {
                        session[params.fields[i]] = json[0][params.fields[i]];
                      }
                    }

                    self.session = new EasyVKSession(self, session);

                    initResolve(self);
                  }
                }
              } else {
                return reject(self._error('empty_response', {
                  response: vkr,
                  where: 'in auth with token (user)'
                }))
              }
            }).catch(e => reject(e))
          }
        } else {
          self.session = session;
          initResolve(self);
        }
      }

      function appToken () {
        let getData;

        let data = {
          access_token: params.access_token,
          v: params.api_v,
          fields: params.fields.join(',')
        };

        if (params.lang !== undefined) data.lang = params.lang;

        getData = data;

        if (self.debuggerRun) {
          try {
            self.debuggerRun.push('request', configuration.BASE_CALL_URL + 'apps.get?' + StaticMethods.urlencode(data));
          } catch (e) {
            // Ignore
          }
        }

        let url = configuration.BASE_CALL_URL + 'apps.get?' + prepareRequest(getData);

        self.debug(EVENT_REQUEST_TYPE, {
          url,
          query: getData,
          method: 'GET'
        });

        fetch$1(url, {
          agent: self.agent,
          headers: {
            'User-Agent': params.userAgent
          }
        }).then(async (res) => {
          let vkr = await res.json();
          if (self.debuggerRun) {
            try {
              self.debuggerRun.push('response', vkr);
            } catch (e) {
              // Ignore
            }
          }

          self.debug(EVENT_RESPONSE_TYPE, { body: vkr });

          if (vkr) {
            let json;

            json = StaticMethods.checkJSONErrors(vkr, (e) => {
              if (e.error_code === 5 || e.error_code === 27 || e.error_code === 28) {
                if (self.params.authType && self.params.authType === authTypes[2]) {
                  return reject(new Error('Is not an application token! Or this token is not valid (expired)'))
                } else {
                  groupToken();
                }
              } else {
                reject(e);
              }
            });

            if (json) {
              json = json.items[0];

              if (Array.isArray(json) && json.length === 0) {
                if (self.params.authType && self.params.authType === authTypes[2]) {
                  return reject(new Error('Is not an application token! Or this token is not valid (expired)'))
                } else {
                  groupToken();
                }
              } else {
                session.app_id = json.id;
                session.app_title = json.title;
                session.app_type = json.type;
                session.app_icons = [json.icon_75, json.icon_150];
                session.author = {
                  id: json.author_id
                };

                session.app_members = json.members_count;

                for (let i = 0; i < params.fields.length; i++) {
                  if (json[params.fields[i]]) {
                    session[params.fields[i]] = json[params.fields[i]];
                  }
                }

                self.session = new EasyVKSession(self, session);

                initResolve(self);
              }
            }
          } else {
            return reject(self._error('empty_response', {
              response: vkr,
              where: 'in auth with token (group)'
            }))
          }
        }).catch(e => reject(e));
      }

      function groupToken () {
        let getData;

        getData = {
          access_token: params.access_token,
          v: params.api_v,
          lang: params.lang || 'ru',
          fields: params.fields.join(',')
        };

        if (self.debuggerRun) {
          try {
            self.debuggerRun.push('request', configuration.BASE_CALL_URL + 'groups.getById?' + StaticMethods.urlencode(getData));
          } catch (e) {
            // Ignore
          }
        }

        let url = configuration.BASE_CALL_URL + 'groups.getById?' + prepareRequest(getData);

        self.debug(EVENT_REQUEST_TYPE, {
          url,
          query: getData,
          method: 'GET'
        });

        fetch$1(url, {
          agent: self.agent,
          headers: {
            'User-Agent': params.userAgent
          }
        }).then(async (res) => {
          let vkr = await res.json();

          if (self.debuggerRun) {
            try {
              self.debuggerRun.push('response', vkr);
            } catch (e) {
              // Ignore
            }
          }

          self.debug(EVENT_RESPONSE_TYPE, { body: vkr });

          if (vkr) {
            let json = StaticMethods.checkJSONErrors(vkr, (e) => {
              if (e.error_code === 100 && self.params.authType && self.params.authType === authTypes[1]) {
                return reject(new Error('Is not a group token! Or this token is not valid (expired)'))
              } else {
                return reject(new Error('EasyVK can not recognize this token authentication type'))
              }
            });

            if (json) {
              if (Array.isArray(json) && json.length === 0) {
                return reject(self._error('access_token_not_valid'))
              } else {
                session = {
                  access_token: session.access_token
                };

                session.group_id = json[0].id;
                session.group_name = json[0].name;
                session.group_screen = json[0].screen_name;

                for (let i = 0; i < params.fields.length; i++) {
                  if (json[0][params.fields[i]]) {
                    session[params.fields[i]] = json[0][params.fields[i]];
                  }
                }

                self.session = new EasyVKSession(self, session);

                initResolve(self);
              }
            }
          } else {
            return reject(self._error('empty_response', {
              response: vkr,
              where: 'in auth with token (group)'
            }))
          }
        }).catch(e => reject(e));
      }

      function getConnectedUtil (UtilObject) {
        if (typeof UtilObject === 'function') return new UtilObject(self)
        return UtilObject
      }

      function connectUtil (prop, util, connectBool) {
        if (connectBool === undefined) {
          connectBool = params.utils[prop] !== false;
        }
        if (connectBool) {
          if (Array.isArray(util)) {
            let utilsWrapper = {};

            util.forEach(util => {
              utilsWrapper[util[0]] = getConnectedUtil(util[1]);
            });

            Object.defineProperty(self, prop, { value: utilsWrapper });
          } else {
            Object.defineProperty(self, prop, { value: getConnectedUtil(util) });
          }
        } else {
          Object.defineProperty(self, prop, {
            get: () => {
              throw new Error(`This util not connected. Make params.utils[${prop}] = true in easyvk options`)
            }
          });
        }
      }

      function initResolve (s) {
        if (params.clear) {
          fs.writeFileSync(params.session_file, '{}');
        }

        let httpConnetBool = (params.utils.http !== false && self.session.user_id) || params.utils.http === true;
        let botsConnectBool = (params.utils.bots !== false && (self.session.group_id || self.session.user_id)) || params.utils.bots === true;
        let longpollConnectBool = (params.utils.longpoll !== false && (self.session.user_id)) || params.utils.longpoll === true;

        connectUtil('uploader', EasyVKUploader);
        connectUtil('widgets', Widgets);
        connectUtil('streamingAPI', StreamingAPIConnector);
        connectUtil('callbackAPI', CallbackAPIConnector);
        connectUtil('http', HTTPEasyVK, httpConnetBool);
        connectUtil('longpoll', LongPollConnector, longpollConnectBool);

        connectUtil('bots', [
          ['longpoll', LongPollConnector$1],
          ['cb', self.callbackAPI ? self.callbackAPI : CallbackAPIConnector]
        ], botsConnectBool);

        self._static = new StaticMethods({
          userAgent: params.userAgent,
          debug: self.debug
        }, self.params);

        self.config = configuration;
        // Here is a middlewares will be saved
        self.middleWares = [async (data) => {
          let next = data.next;
          data.next = undefined;
          await next(data);
        }];

        self._middlewaresController = new MiddlewaresMechanism(self);

        // http module for http requests from cookies and jar session

        // Re init all cases
        self.session = new EasyVKSession(self, self.session);

        if (params.save_session) {
          return self.session.save().then(() => {
            return resolve(s)
          }).catch(reject)
        }

        return resolve(s)
      }
    })
  }

  async post (methodName, data, other) {
    let self = this;

    return new Promise((resolve, reject) => {
      return self.call(methodName, data, 'post', other).then(resolve, reject)
    })
  }

  /**
 *
 *Function for calling to methods and get anything form VKontakte API
 *See more: https://vk.com/dev/methods
 *
 *@param {String} methodName - Is just a method name which you need to call and get data,
 *  for example: "messages.send", "users.get"
 *@param {Object} [data={}] - Is data object for query params, it will be serialized from object to uri string.
 *  If vk.com asks a parameters, you can send they.
 *  (Send access_token to this from session is not necessary, but also you can do this)
 *@param {String} [methodType=get] - Is type for query, ["post", "delete", "get", "put"]
 *
 *  @return {Promise}
 *  @promise Call to a method, send request for VKontakte API
 *  @resolve {Object} - Standard object like {vk: EasyVK, vkr: Response}
 *  @reject {Error} - vk.com error response or node-fetch module error
     *
 */

  async call (methodName, data = {}, methodType = 'get', other = {}) {
    let self = this;

    let {
      middleWare
    } = other;

    let highloadStack = null;

    return new Promise((resolve, reject) => {
      async function reCall (_needSolve, _resolverReCall, _rejecterReCall) {
        methodType = String(methodType).toLowerCase();

        if (methodType !== 'post') {
          methodType = 'get';
        }

        if (!StaticMethods.isObject(data)) reject(new Error('Data must be an object'));
        if (!data.access_token) data.access_token = self.session.access_token;
        if (!data.v) data.v = self.params.api_v;

        if (!data.captcha_sid && self.params.captcha_sid) data.captcha_sid = self.params.captcha_sid;
        if (!data.captcha_key && self.params.captcha_key) data.captcha_key = self.params.captcha_key;

        if (!data.lang && self.params.lang && self.params.lang !== 'undefined') {
          data.lang = self.params.lang || 'ru';
        }

        if (middleWare && typeof middleWare === 'function') {
          data = await middleWare(data);
        }

        let thread = {
          methodType,
          method: methodName,
          query: data,
          _needSolve
        };

        let FromMiddleWare = await self._middlewaresController.run(thread);

        methodName = FromMiddleWare.method;
        methodType = FromMiddleWare.methodType;

        data = FromMiddleWare.query;

        return self._static.call(methodName, data, methodType, self._debugger, self.agent, {
          signal: other.signal
        }).then((vkr) => {
          if (_needSolve) {
            try {
              _resolverReCall(true);
            } catch (e) {}
          }

          if (highloadStack) {
            highloadStack.forEach((stack, i) => {
              if (i === highloadStack.length - 1) { return resolve(vkr) }
              return stack.resolve(vkr)
            });
          } else {
            return resolve(vkr)
          }
        }).catch((err) => {
          try {
            if (err.highload) {
              data = {
                access_token: err.highload.token || self.params.access_token,
                ...(err.highload.data)
              };

              highloadStack = err.highload.stack;

              methodName = 'execute';
            }

            self._catchCaptcha({
              err,
              reCall,
              _needSolve,
              _resolverReCall,
              _rejecterReCall,
              data,
              reject
            });
          } catch (e) {
            if (highloadStack) {
              highloadStack.forEach((stack, i) => {
                if (i === highloadStack.length - 1) { return reject(err) }
                return stack.reject(err)
              });
            } else {
              reject(err);
            }
          }
        })
      }

      reCall();
    })
  }

  _catchCaptcha (params = {}) {
    let self = this;

    let { err, reCall, _needSolve, _rejecterReCall, data, reject } = params;

    let vkr = JSON.parse(err.message);

    if (vkr.error === 'need_captcha' || vkr.error.error_code === 14) {
      if (_needSolve) {
        try {
          _rejecterReCall({
            error: false,
            reCall: () => {
              return reCall()
            }
          });
        } catch (e) {}

        return
      }

      // Captcha error, handle it
      const captchaSid = vkr.error.captcha_sid || vkr.captcha_sid;
      const captchaImg = vkr.error.captcha_img || vkr.captcha_img;
      let paramsForHandler = { captcha_sid: captchaSid, captcha_img: captchaImg, vk: self, params: data };

      paramsForHandler.resolve = (captchaKey) => {
        return new Promise((resolve, reject) => {
          data.captcha_key = captchaKey;
          data.captcha_sid = captchaSid;

          try {
            reCall('NEED SOLVE', resolve, reject, data);
          } catch (errorRecall) { /* Need pass it */ }
        })
      };

      self.captchaHandler(paramsForHandler);
    } else {
      reject(err);
    }
  }

  // Ony for mer
  _error (...args) {
    let self = this;
    return self._errors.error(...args)
  }
}

const is = (obj1, obj2) => Object.getPrototypeOf(obj1).constructor.name === obj2;

const classes = {
  VKResponse: 'VKResponse',
  VKResponseError: 'VKResponseError',
  EasyVKError: 'EasyVKError'
};

const version = '2.7.35';
const callbackAPI = new CallbackAPIConnector({});
const streamingAPI = new StreamingAPIConnector({});

const debuggerRun = new RequestsDebugger(Boolean(false));
const authTypes$1 = ['user', 'group', 'app'];

/**
 *
 *  This function check all parameters
 *  @see createSession()
 *  @return {Promise}
 *  @promise Check errors
 *  @resolve {Object} changed user parameters for create session
 *  @reject {Error} auth error or just an error from responses
 *
 */

async function checkInitParams (params = {}) {
  return new Promise((resolve, reject) => {
    if (params.save !== undefined) {
      params.save_session = params.save;
    }

    if (params.token !== undefined) {
      params.access_token = params.token;
    }

    if (params.v !== undefined) {
      params.api_v = params.v;
    }

    if (params.captchaSid !== undefined) {
      params.captcha_sid = params.captchaSid;
    }

    if (params.captchaKey !== undefined) {
      params.captcha_key = params.captchaKey;
    }

    if (params.clientId !== undefined) {
      params.client_id = params.clientId;
    }

    if (params.clientSecret !== undefined) {
      params.client_secret = params.clientSecret;
    }

    if (params.sessionFile !== undefined) {
      params.session_file = params.sessionFile;
    }

    if (params.save_session !== false) {
      params.save_session = configuration.save_session;
    }

    if (params.session_file) {
      if (!StaticMethods.isString(params.session_file)) {
        return reject(new Error('The session_file must be a string'))
      }
    } else {
      params.session_file = configuration.session_file;
    }

    if (params.api_v && params.api_v !== configuration.api_v) {
      if (isNaN(params.api_v.toString())) {
        return reject(new Error('The api_v parameter must be numeric'))
      } else if (Number(params.api_v) < 5) {
        return reject(new Error('The api_v parameter must be more then 5.0 version, other not support'))
      }
    } else {
      params.api_v = configuration.api_v;
    }

    if (params.captcha_key && !params.captcha_sid) {
      return reject(new Error('You puted captcha_key but not using captcha_sid parameter'))
    } else if (!params.captcha_key && params.captcha_sid) {
      return reject(new Error('You puted captcha_sid but not puted captcha_key parameter'))
    } else if (params.captcha_key && params.captcha_sid) {
      if (isNaN(params.captcha_sid.toString())) {
        return reject(new Error('The captcha_sid must be numeric'))
      }
    }

    if (params.reauth !== true) {
      params.reauth = configuration.reauth;
    }

    if (params.reauth) {
      if (params.access_token && params.username) {
        return reject(new Error('Select only one way auth: access_token XOR username'))
      }

      if (params.access_token) {
        if (!StaticMethods.isString(params.access_token)) {
          return reject(new Error('The access_token must be a string'))
        }
      }

      if (params.username && !params.password) {
        return reject(new Error('Put password if you want aut with username'))
      }

      if (params.username && params.password) {
        params.username = params.username.toString();
        params.password = params.password.toString();
      }
    }

    if (!params.client_id || !params.client_secret) {
      params.client_id = configuration['ANDROID_CLIENT_ID'];
      params.client_secret = configuration['ANDROID_CLIENT_SECRET'];
    }

    params.lang = String(params.lang);

    if (!params.lang) {
      params.lang = 'ru';
    }

    if (StaticMethods.isString(params.fields)) {
      params.fields = params.fields.split(',');
    }

    if (!params.fields || !Array.isArray(params.fields)) {
      params.fields = [];
    } else {
      params.fields = params.fields.map(a => String(a));
    }

    if (!params.userAgent) {
      params.userAgent = configuration.DEFAULT_USER_AGENT;
    }

    if (!params.utils) {
      params.utils = configuration.DEFAULT_UTILS;
    }

    if (!params.mode) {
      params.mode = '';
    }

    if (StaticMethods.isString(params.mode)) {
      params.mode = {
        name: params.mode
      };
    }

    if (!params.mode.name) {
      params.mode.name = 'default';
    }

    if (params.authType && authTypes$1.indexOf(params.authType) === -1) {
      return reject(new Error('Auth type must be contents in ' + JSON.stringify(authTypes$1)))
    } else if (params.authType && !params.access_token) {
      params.authType = null;
    }

    if (params.debug && !(params.debug instanceof Debugger)) return reject(new Error('Debug parameter must instances only from Debugger class'))

    if (params.onlyInstance !== true) params.onlyInstance = false;

    resolve(params);
  })
}

/*
 *  This function check you easyVK(params) parameters
 *  @param {Object} params - Settings for authentication, for create session
 *  @param {Boolean} [params.save_session=true] - If is true then session will be saved in params.session_file file
 *  @param {(String|Number)} [params.api_v=5.73] - API version for all requests, I am
 *  recommend you use API version >= 5
 *  @param {String} [params.access_token=] - Your access token, group or user. If is user token then
 *  easyVK will get user_id for you, else [group_id, screen_name, group_name] for session file
 *  @param {String|Number} [params.username] - Your login for authenticate, your_email@example.com or +7(916)7888886 (example)
 *  It need only if you puted params.password and not puted params.access_token parameter
 *  @param {String|Number} [params.password] - Your password for user account, it will be authenticated
 *  from windows app_id, from official client. I am not saving your data for hack, all is opened for you
 *  @param {Boolean} [params.reauth=false] - Need ignore session file and log in with newest parameters?
 *  @param {String} [params.session_file=.vksession] - Path for your session file, i am recommend you to use the path module
 *  for create path.join(__dirname, '.session-vk')
 *  @param {String|Number} [params.code] - Is your code from application which generate your 2-factor-auth
 *  code
 *  @param {String} [params.captcha_key] - Is your code from captcha, only if you got an error and not solved it
 *  before
 *  @param {String|Number} [params.captcha_sid] - Is a captcha id from captcha error, if you got it and not solved before
 *  @param {Function|Async Function} [params.captchaHandler] - Is a captcha Handler function for
 *  handle all captcha errors
 *
 *  @promise Authenticate you and create session
 *  @resolve {Object} EasyVK object, which contents session and all methods
 *  for work with VKontakte API
 *
 */

const Auth = async (params = {}) => {
  return checkInitParams(params).then((p) => {
    let vk = new EasyVK(p, debuggerRun);
    return vk._init()
  })
};

// That's realy bad :(
Auth.static = StaticMethods;
Auth.debuggerRun = debuggerRun;
Auth.Debugger = Debugger;
Auth.callbackAPI = callbackAPI;
Auth.call = StaticMethods.call;
Auth.randomId = StaticMethods.randomId;
Auth.createExecute = StaticMethods.createExecute;
Auth.authTypes = authTypes$1;
Auth.version = version;
Auth.streamingAPI = streamingAPI;
Auth.is = is;
Auth.class = classes;
Auth.AbortController = AbortController;

module.exports = Auth;
