vk-io-stable
Version:
Модуль для создания бота VK
2,679 lines (2,175 loc) • 228 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var nodeHttps = require('https');
var nodeHttps__default = _interopDefault(nodeHttps);
var nodeUtil = require('util');
var nodeUtil__default = _interopDefault(nodeUtil);
var fetch = _interopDefault(require('node-fetch'));
var createDebug = _interopDefault(require('debug'));
var nodeUrl = _interopDefault(require('url'));
var cheerio = _interopDefault(require('cheerio'));
var toughCookie = _interopDefault(require('tough-cookie'));
var nodeCrypto = _interopDefault(require('crypto'));
var nodeFs = _interopDefault(require('fs'));
var nodeStream = _interopDefault(require('stream'));
var sandwichStream = require('sandwich-stream');
var middlewareIo = require('middleware-io');
var nodeHttp = _interopDefault(require('http'));
var WebSocket = _interopDefault(require('ws'));
/**
* Creates a key and value from the keys
*
* @param {string[]} keys
*
* @return {Object}
*/
const keyMirror = (keys) => {
const out = {};
for (const key of keys) {
out[key] = key;
}
return out;
};
/**
* Returns method for execute
*
* @param {string} method
* @param {Object} params
*
* @return {string}
*/
const getExecuteMethod = (method, params = {}) => {
const options = {};
for (const [key, value] of Object.entries(params)) {
options[key] = typeof value === 'object'
? String(value)
: value;
}
return `API.${method}(${JSON.stringify(options)})`;
};
/**
* Returns chain for execute
*
* @param {Array} methods
*
* @return {string}
*/
const getChainReturn = methods => (
`return [${methods.join(',')}];`
);
/**
* Resolve task
*
* @param {Array} tasks
* @param {Array} results
*/
const resolveExecuteTask = (tasks, result) => {
let errors = 0;
result.response.forEach((response, i) => {
if (response !== false) {
tasks[i].resolve(response);
return;
}
tasks[i].reject(result.errors[errors]);
errors += 1;
});
};
/**
* Returns random ID
*
* @return {number}
*/
const getRandomId = () => (
`${Math.floor(Math.random() * 1e4)}${Date.now()}`
);
/**
* Delay N-ms
*
* @param {number} delayed
*
* @return {Promise}
*/
const delay = delayed => (
new Promise(resolve => setTimeout(resolve, delayed))
);
const lt = /</g;
const qt = />/g;
const br = /<br>/g;
const amp = /&/g;
const quot = /"/g;
/**
* Decodes HTML entities
*
* @param {string} text
*
* @return {string}
*/
const unescapeHTML = text => (
text
.replace(lt, '<')
.replace(qt, '>')
.replace(br, '\n')
.replace(amp, '&')
.replace(quot, '"')
);
/**
* Copies object params to new object
*
* @param {Object} params
* @param {Array} properties
*
* @return {Object}
*/
const copyParams = (params, properties) => {
const copies = {};
for (const property of properties) {
copies[property] = params[property];
}
return copies;
};
/**
* Displays deprecated message
*
* @param {string} message
*/
const showDeprecatedMessage = (message) => {
// eslint-disable-next-line no-console
console.log(' \u001b[31mDeprecated:\u001b[39m', message);
};
const { inspect } = nodeUtil__default;
class Request {
/**
* Constructor
*
* @param {string} method
* @param {Object} params
*/
constructor(method, params = {}) {
this.method = method;
this.params = { ...params };
this.attempts = 0;
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
/**
* Returns custom tag
*
* @return {string}
*/
get [Symbol.toStringTag]() {
return 'Request';
}
/**
* Adds attempt
*
* @return {number}
*/
addAttempt() {
this.attempts += 1;
return this.attempts;
}
/**
* Returns string to execute
*
* @return {string}
*/
toString() {
return getExecuteMethod(this.method, this.params);
}
/**
* Custom inspect object
*
* @param {?number} depth
* @param {Object} options
*
* @return {string}
*/
[inspect.custom](depth, options) {
const { name } = this.constructor;
const { method, params, promise } = this;
const payload = { method, params, promise };
return `${options.stylize(name, 'special')} ${inspect(payload, options)}`;
}
}
/**
* General error class
*
* @public
*/
class VKError extends Error {
/**
* Constructor
*
* @param {Object} payload
*/
constructor({ code, message }) {
super(message);
this.code = code;
this.message = message;
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
/**
* Returns custom tag
*
* @return {string}
*/
get [Symbol.toStringTag]() {
return this.constructor.name;
}
/**
* Returns property for json
*
* @return {Object}
*/
toJSON() {
const json = {};
for (const key of Object.getOwnPropertyNames(this)) {
json[key] = this[key];
}
return json;
}
}
var version = "4.0.0-rc.18";
/**
* VK API version
*
* @type {string}
*/
const API_VERSION = '5.95';
/**
* Chat peer ID
*
* @type {number}
*/
const CHAT_PEER = 2e9;
/**
* Blank html redirect
*
* @type {string}
*/
const CALLBACK_BLANK = 'https://oauth.vk.com/blank.html';
/**
* User-Agent for standalone auth
*
* @type {string}
*/
const DESKTOP_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36';
/**
* Minimum time interval api with error
*
* @type {number}
*/
const MINIMUM_TIME_INTERVAL_API = 1133;
/**
* Default options
*
* @type {Object}
*
* @property {?string} [token] Access token
* @property {Agent} [agent] HTTPS agent
* @property {?string} [language] The return data language
*
* @property {?number} [appId] Application ID
* @property {?number} [appSecret] Secret application key
*
* @property {?string} [login] User login (phone number or email)
* @property {?string} [phone] User phone number
* @property {?string} [password] User password
*
* @property {?number} [authScope] List of permissions
* @property {?number} [authTimeout] Wait time for one auth request
*
* @property {string} [apiMode] Query mode (sequential|parallel|parallel_selected)
* @property {number} [apiWait] Time to wait before re-querying
* @property {number} [apiLimit] Requests per second
* @property {string} [apiBaseUrl] Base API URL
* @property {number} [apiTimeout] Wait time for one request
* @property {number} [apiHeaders] Headers sent to the API
* @property {number} [apiAttempts] The number of retries at calling
* @property {number} [apiExecuteCount] Number of requests per execute
* @property {Array} [apiExecuteMethods] Methods for call execute (apiMode=parallel_selected)
*
* @property {number} [uploadTimeout] Wait time for one request
*
* @property {number} [pollingWait] Time to wait before re-querying
* @property {number} [pollingGroupId] Group ID for polling
* @property {number} [pollingAttempts] The number of retries at calling
*
* @property {?string} [webhookSecret] Webhook secret key
* @property {?string} [webhookConfirmation] Webhook confirmation key
*
* @property {number} [collectAttempts] The number of retries at calling
*/
const defaultOptions = {
token: null,
agent: null,
language: null,
appId: null,
appSecret: null,
login: null,
phone: null,
password: null,
authScope: 'all',
authTimeout: 10e3,
apiMode: 'sequential',
apiWait: 3e3,
apiLimit: 3,
apiBaseUrl: 'https://api.vk.com/method',
apiAttempts: 3,
apiTimeout: 10e3,
apiHeaders: {
'User-Agent': `vk-io/${version} (+https://github.com/negezor/vk-io)`
},
apiExecuteCount: 25,
apiExecuteMethods: ['messages.send'],
uploadTimeout: 20e3,
pollingWait: 3e3,
pollingAttempts: 3,
pollingGroupId: null,
webhookSecret: null,
webhookConfirmation: null,
collectAttempts: 3
};
/**
* The attachment types
*
* @type {Object}
*/
const attachmentTypes = {
AUDIO: 'audio',
AUDIO_MESSAGE: 'audio_message',
GRAFFITI: 'graffiti',
DOCUMENT: 'doc',
GIFT: 'gift',
LINK: 'link',
MARKET_ALBUM: 'market_album',
MARKET: 'market',
PHOTO: 'photo',
STICKER: 'sticker',
VIDEO: 'video',
WALL_REPLY: 'wall_reply',
WALL: 'wall',
POLL: 'poll'
};
/**
* Default extensions for attachments
*
* @type {Object}
*/
const defaultExtensions = {
photo: 'jpg',
video: 'mp4',
audio: 'mp3',
graffiti: 'png',
audioMessage: 'ogg'
};
/**
* Default content type for attachments
*
* @type {Object}
*/
const defaultContentTypes = {
photo: 'image/jpeg',
video: 'video/mp4',
audio: 'audio/mp3',
graffiti: 'image/png',
audioMessage: 'audio/ogg'
};
/**
* Sources of captcha
*
* @type {Object}
*/
const captchaTypes = keyMirror([
'API',
'DIRECT_AUTH',
'IMPLICIT_FLOW_AUTH',
'ACCOUNT_VERIFICATION'
]);
/**
* Message source
*
* @type {Object}
*/
const messageSources = {
USER: 'user',
CHAT: 'chat',
GROUP: 'group',
EMAIL: 'email'
};
/**
* Resource types
*
* @type {Object}
*/
const resourceTypes = {
USER: 'user',
GROUP: 'group',
APPLICATION: 'application'
};
/**
* API error codes
*
* @type {Object}
*/
const apiErrors = {
UNKNOWN_ERROR: 1,
APP_SWITCHED_OFF: 2,
UNKNOWN_METHOD: 3,
INVALID_SIGNATURE: 4,
AUTH_FAILURE: 5,
TOO_MANY_REQUESTS: 6,
SCOPE_NEEDED: 7,
INCORRECT_REQUEST: 8,
TOO_MANY_SIMILAR_ACTIONS: 9,
INTERNAL_ERROR: 10,
RESPONSE_SIZE_TOO_BIG: 13,
CAPTCHA_REQUIRED: 14,
ACCESS_DENIED: 15,
USER_VALIDATION_REQUIRED: 17,
PAGE_BLOCKED: 18,
STANDALONE_ONLY: 20,
STANDALONE_AND_OPEN_API_ONLY: 21,
METHOD_DISABLED: 23,
CONFIRMATION_REQUIRED: 24,
GROUP_TOKEN_NOT_VALID: 27,
APP_TOKEN_NOT_VALID: 28,
METHOD_CALL_LIMIT: 29,
PROFILE_IS_PRIVATE: 30,
WRONG_PARAMETER: 100,
INVALID_APPLICATION_ID: 101,
LIMIT_ENTRY_EXHAUSTED: 103,
INCORRECT_USER_ID: 113,
INVALID_TIMESTAMP: 150,
ALBUM_ACCESS_DENIED: 200,
AUDIO_ACCESS_DENIED: 201,
GROUP_ACCESS_DENIED: 203,
ALBUM_OVERFLOW: 300,
PAYMENTS_DISABLED: 500,
COMMERCIAL_ACCESS_DENIED: 600,
COMMERCIAL_ERROR: 603,
BLACKLISTED_USER: 900,
MESSAGE_COMMUNITY_BLOCKED_BY_USER: 901,
MESSAGE_BLOCKED_BY_USER_PRIVACY: 902,
UNABLE_TO_EDIT_MESSAGE_AFTER_DAY: 909,
MESSAGE_CANNOT_EDIT_IS_TOO_LONG: 910,
KEYBOARD_FORMAT_IS_INVALID: 911,
CHAT_BOT_FEATURE: 912,
TOO_MANY_FORWARDED_MESSAGES: 913,
MESSAGE_TOO_LONG: 914,
NO_ACCESS_TO_CONVERSATION: 917,
CANNOT_EDIT_THIS_TYPE_MESSAGE: 920,
UNABLE_TO_FORWARD_MESSAGES: 921,
UNABLE_TO_DELETE_MESSAGE_FOR_RECIPIENTS: 924,
NOT_ADMIN_CHAT: 925,
COMMUNITY_CANNOT_INTERACT_WITH_THIS_PEER: 932,
CONTACT_NOT_FOUND: 936
};
/**
* Auth error codes
*
* @type {Object}
*/
const authErrors = keyMirror([
'PAGE_BLOCKED',
'INVALID_PHONE_NUMBER',
'AUTHORIZATION_FAILED',
'FAILED_PASSED_CAPTCHA',
'FAILED_PASSED_TWO_FACTOR',
]);
/**
* Upload error codes
*
* @type {Object}
*/
const uploadErrors = keyMirror([
'MISSING_PARAMETERS',
'NO_FILES_TO_UPLOAD',
'EXCEEDED_MAX_FILES',
'UNSUPPORTED_SOURCE_TYPE'
]);
/**
* Updates error codes
*
* @type {Object}
*/
const updatesErrors = keyMirror([
'NEED_RESTART',
'POLLING_REQUEST_FAILED'
]);
/**
* Collect error codes
*
* @type {Object}
*/
const collectErrors = keyMirror([
'EXECUTE_ERROR'
]);
/**
* Snippets error codes
*
* @type {Object}
*/
const snippetsErrors = keyMirror([
'INVALID_URL',
'INVALID_RESOURCE',
'RESOURCE_NOT_FOUND'
]);
/**
* Snippets error codes
*
* @type {Object}
*/
const sharedErrors = keyMirror([
'MISSING_CAPTCHA_HANDLER',
'MISSING_TWO_FACTOR_HANDLER'
]);
/**
* Updates sources
*
* @type {Object}
*/
const updatesSources = keyMirror([
'POLLING',
'WEBHOOK'
]);
/**
* List of user permissions and their bit mask
*
* @type {Map}
*/
const userScopes = new Map([
['notify', 1],
['friends', 2],
['photos', 4],
['audio', 8],
['video', 16],
['pages', 128],
['link', 256],
['status', 1024],
['notes', 2048],
['messages', 4096],
['wall', 8192],
['ads', 32768],
['offline', 65536],
['docs', 131072],
['groups', 262144],
['notifications', 524288],
['stats', 1048576],
['email', 4194304],
['market', 134217728]
]);
/**
* List of group permissions and their bit mask
*
* @type {Map}
*/
const groupScopes = new Map([
['stories', 1],
['photos', 4],
// ['app_widget', 64],
['messages', 4096],
['docs', 131072],
['manage', 262144]
]);
/**
* VK Platforms
*
* @type {Map}
*/
const platforms = new Map([
[1, 'mobile'],
[2, 'iphone'],
[3, 'ipad'],
[4, 'android'],
[5, 'wphone'],
[6, 'windows'],
[7, 'web'],
[8, 'standalone']
]);
/**
* Parse attachments with RegExp
*
* @type {RegExp}
*/
const parseAttachment = /(photo|video|audio|doc|audio_message|graffiti|wall|market|poll|gift)([-\d]+)_(\d+)_?(\w+)?/;
/**
* Parse resource with RegExp
*
* @type {RegExp}
*/
const parseResource = /(id|club|public|albums|tag|app(?:lication))([-\d]+)/;
/**
* Parse owner resource with RegExp
*
* @type {RegExp}
*/
const parseOwnerResource = /(album|topic|wall|page|videos)([-\d]+)_(\d+)/;
/**
* Inspect custom data
*
* @type {Symbol}
*/
const inspectCustomData = Symbol('inspectCustomData');
const { CAPTCHA_REQUIRED, USER_VALIDATION_REQUIRED, CONFIRMATION_REQUIRED } = apiErrors;
class APIError extends VKError {
/**
* Constructor
*
* @param {Object} payload
*/
constructor(payload) {
const code = Number(payload.error_code);
const message = `Code №${code} - ${payload.error_msg}`;
super({ code, message });
this.params = payload.request_params;
if (code === CAPTCHA_REQUIRED) {
this.captchaSid = Number(payload.captcha_sid);
this.captchaImg = payload.captcha_img;
} else if (code === USER_VALIDATION_REQUIRED) {
this.redirectUri = payload.redirect_uri;
} else if (code === CONFIRMATION_REQUIRED) {
this.confirmationText = payload.confirmation_text;
}
}
}
const { DEBUG = '' } = process.env;
const isDebug = DEBUG.includes('vk-io:auth');
class AuthError extends VKError {
/**
* Constructor
*
* @param {Object} payload
*/
constructor({ message, code, pageHtml = null }) {
super({ message, code });
this.pageHtml = isDebug
? pageHtml
: null;
}
}
class UploadError extends VKError {}
class CollectError extends VKError {
/**
* Constructor
*
* @param {Object} payload
*/
constructor({ message, code, errors }) {
super({ message, code });
this.errors = errors;
}
}
class UpdatesError extends VKError {}
class ExecuteError extends VKError {
/**
* Constructor
*
* @param {Object} payload
*/
constructor(payload) {
const code = Number(payload.error_code);
const message = `Code №${code} - ${payload.error_msg}`;
super({ code, message });
this.method = payload.method;
}
}
class SnippetsError extends VKError {}
class StreamingRuleError extends VKError {
/**
* Constructor
*
* @param {Object} payload
*/
constructor({ message, error_code: code }) {
super({ message, code });
}
}
const { URL } = nodeUrl;
/**
* Returns the entire permission bit mask
*
* @return {number}
*/
const getAllUsersPermissions = () => (
Array.from(userScopes.values()).reduce((previous, current) => (
previous + current
), 0)
);
/**
* Returns the entire permission bit mask
*
* @return {number}
*/
const getAllGroupsPermissions = () => (
Array.from(groupScopes.values()).reduce((previous, current) => (
previous + current
), 0)
);
/**
* Returns the bit mask of the user permission by name
*
* @param {Array|string} scope
*
* @return {number}
*/
const getUsersPermissionsByName = (scope) => {
if (!Array.isArray(scope)) {
scope = scope.split(/,\s{0,}/);
}
let bitMask = 0;
for (const name of scope) {
if (userScopes.has(name)) {
bitMask += userScopes.get(name);
}
}
return bitMask;
};
/**
* Returns the bit mask of the group permission by name
*
* @param {Array|string} scope
*
* @return {number}
*/
const getGroupsPermissionsByName = (scope) => {
if (!Array.isArray(scope)) {
scope = scope.split(/,\s{0,}/);
}
let bitMask = 0;
for (const name of scope) {
if (groupScopes.has(name)) {
bitMask += groupScopes.get(name);
}
}
return bitMask;
};
/**
* Parse form
*
* @param {Cheerio} $
*
* @return {Object}
*/
const parseFormField = ($) => {
const $form = $('form[action][method]');
const fields = {};
for (const { name, value } of $form.serializeArray()) {
fields[name] = value;
}
return {
action: $form.attr('action'),
fields
};
};
/**
* Returns full URL use Response
*
* @param {string} action
* @param {Response} response
*
* @type {URL}
*/
const getFullURL = (action, { url }) => {
if (action.startsWith('https://')) {
return new URL(action);
}
const { protocol, host } = new URL(url);
return new URL(action, `${protocol}//${host}`);
};
const { promisify } = nodeUtil__default;
const debug = createDebug('vk-io:util:fetch-cookie');
const REDIRECT_CODES = [303, 301, 302];
const { CookieJar } = toughCookie;
const USER_AGENT_RE = /^User-Agent$/i;
const findUserAgent = (headers) => {
if (!headers) {
return null;
}
const key = Object.keys(headers)
.find(header => USER_AGENT_RE.test(header));
if (!key) {
return null;
}
return headers[key];
};
const fetchCookieDecorator = (jar = new CookieJar()) => {
const setCookie = promisify(jar.setCookie).bind(jar);
const getCookieString = promisify(jar.getCookieString).bind(jar);
return async function fetchCookie(url, options = {}) {
const previousCookie = await getCookieString(url);
const { headers = {} } = options;
if (previousCookie) {
headers.cookie = previousCookie;
}
debug('fetch url %s', url);
const response = await fetch(url, {
...options,
headers
});
const { 'set-cookie': cookies = [] } = response.headers.raw();
if (cookies.length === 0) {
return response;
}
await Promise.all(cookies.map(cookie => (
setCookie(cookie, response.url)
)));
return response;
};
};
const fetchCookieFollowRedirectsDecorator = (jar) => {
const fetchCookie = fetchCookieDecorator(jar);
return async function fetchCookieFollowRedirects(url, options = {}) {
const response = await fetchCookie(url, {
...options,
redirect: 'manual'
});
const isRedirect = REDIRECT_CODES.includes(response.status);
if (isRedirect && options.redirect !== 'manual' && options.follow !== 0) {
debug('Redirect to', response.headers.get('location'));
let follow;
if (options.follow) {
follow = options.follow - 1;
}
const userAgent = findUserAgent(options.headers);
const headers = userAgent
? { 'User-Agent': userAgent }
: {};
const redirectResponse = await fetchCookieFollowRedirects(response.headers.get('location'), {
method: 'GET',
body: null,
headers,
follow
});
return redirectResponse;
}
return response;
};
};
const { load: cheerioLoad } = cheerio;
const { URL: URL$1, URLSearchParams } = nodeUrl;
const debug$1 = createDebug('vk-io:auth:account-verification');
const {
INVALID_PHONE_NUMBER,
AUTHORIZATION_FAILED,
FAILED_PASSED_CAPTCHA,
FAILED_PASSED_TWO_FACTOR
} = authErrors;
/**
* Two-factor auth check action
*
* @type {string}
*/
const ACTION_AUTH_CODE = 'act=authcheck';
/**
* Phone number check action
*
* @type {string}
*/
const ACTION_SECURITY_CODE = 'act=security';
/**
* Bind a phone to a page
*
* @type {string}
*/
const ACTION_VALIDATE = 'act=validate';
/**
* Bind a phone to a page action
*
* @type {string}
*/
const ACTION_CAPTCHA = 'act=captcha';
/**
* Number of two-factorial attempts
*
* @type {number}
*/
const TWO_FACTOR_ATTEMPTS = 3;
class AccountVerification {
/**
* Constructor
*
* @param {VK} vk
*/
constructor(vk) {
this.vk = vk;
const { agent, login, phone } = vk.options;
this.login = login;
this.phone = phone;
this.agent = agent;
this.jar = new CookieJar();
this.fetchCookie = fetchCookieFollowRedirectsDecorator(this.jar);
this.captchaValidate = null;
this.captchaAttempts = 0;
this.twoFactorValidate = null;
this.twoFactorAttempts = 0;
}
/**
* Returns custom tag
*
* @return {string}
*/
get [Symbol.toStringTag]() {
return 'AccountVerification';
}
/**
* Executes the HTTP request
*
* @param {string} url
* @param {Object} options
*
* @return {Promise<Response>}
*/
fetch(url, options = {}) {
const { agent } = this;
const { headers = {} } = options;
return this.fetchCookie(url, {
...options,
agent,
timeout: this.vk.options.authTimeout,
compress: false,
headers: {
...headers,
'User-Agent': DESKTOP_USER_AGENT
}
});
}
/**
* Runs authorization
*
* @return {Promise<Object>}
*/
// eslint-disable-next-line consistent-return
async run(redirectUri) {
let response = await this.fetch(redirectUri, {
method: 'GET'
});
const isProcessed = true;
while (isProcessed) {
const { url } = response;
if (url.includes(CALLBACK_BLANK)) {
let { hash } = new URL$1(response.url);
if (hash.startsWith('#')) {
hash = hash.substring(1);
}
const params = new URLSearchParams(hash);
if (params.has('error')) {
throw new AuthError({
message: `Failed passed grant access: ${params.get('error_description') || 'Unknown error'}`,
code: AUTHORIZATION_FAILED
});
}
const user = params.get('user_id');
return {
user: user !== null
? Number(user)
: null,
token: params.get('access_token')
};
}
const $ = cheerioLoad(await response.text());
if (url.includes(ACTION_AUTH_CODE)) {
response = await this.processTwoFactorForm(response, $);
continue;
}
if (url.includes(ACTION_SECURITY_CODE)) {
response = await this.processSecurityForm(response, $);
continue;
}
if (url.includes(ACTION_VALIDATE)) {
response = await this.processValidateForm(response, $);
continue;
}
if (url.includes(ACTION_CAPTCHA)) {
response = await this.processCaptchaForm(response, $);
continue;
}
throw new AuthError({
message: 'Account verification failed',
code: AUTHORIZATION_FAILED
});
}
}
/**
* Process two-factor form
*
* @param {Response} response
* @param {Cheerio} $
*
* @return {Promise<Response>}
*/
async processTwoFactorForm(response, $) {
debug$1('process two-factor handle');
if (this.twoFactorValidate !== null) {
this.twoFactorValidate.reject(new AuthError({
message: 'Incorrect two-factor code',
code: FAILED_PASSED_TWO_FACTOR,
pageHtml: $.html()
}));
this.twoFactorAttempts += 1;
}
if (this.twoFactorAttempts >= TWO_FACTOR_ATTEMPTS) {
throw new AuthError({
message: 'Failed passed two-factor authentication',
code: FAILED_PASSED_TWO_FACTOR
});
}
const { action, fields } = parseFormField($);
const { code, validate } = await this.vk.callbackService.processingTwoFactor({});
fields.code = code;
try {
const url = getFullURL(action, response);
response = await this.fetch(url, {
method: 'POST',
body: new URLSearchParams(fields)
});
return response;
} catch (error) {
validate.reject(error);
throw error;
}
}
/**
* Process security form
*
* @param {Response} response
* @param {Cheerio} $
*
* @return {Promise<Response>}
*/
async processSecurityForm(response, $) {
debug$1('process security form');
const { login, phone } = this;
let number;
if (phone !== null) {
number = phone;
} else if (login !== null && !login.includes('@')) {
number = login;
} else {
throw new AuthError({
message: 'Missing phone number in the phone or login field',
code: INVALID_PHONE_NUMBER
});
}
if (typeof number === 'string') {
number = number.trim().replace(/^(\+|00)/, '');
}
number = String(number);
const $field = $('.field_prefix');
const prefix = $field.first().text().trim().replace('+', '').length;
const postfix = $field.last().text().trim().length;
const { action, fields } = parseFormField($);
fields.code = number.slice(prefix, number.length - postfix);
const url = getFullURL(action, response);
response = await this.fetch(url, {
method: 'POST',
body: new URLSearchParams(fields)
});
if (response.url.includes(ACTION_SECURITY_CODE)) {
throw new AuthError({
message: 'Invalid phone number',
code: INVALID_PHONE_NUMBER
});
}
return response;
}
/**
* Process validation form
*
* @param {Response} response
* @param {Cheerio} $
*
* @return {Promise<Response>}
*/
processValidateForm(response, $) {
const href = $('#activation_wrap a').attr('href');
const url = getFullURL(href, response);
return this.fetch(url, {
method: 'GET'
});
}
/**
* Process captcha form
*
* @param {Response} response
* @param {Cheerio} $
*
* @return {Promise<Response>}
*/
async processCaptchaForm(response, $) {
if (this.captchaValidate !== null) {
this.captchaValidate.reject(new AuthError({
message: 'Incorrect captcha code',
code: FAILED_PASSED_CAPTCHA
}));
this.captchaValidate = null;
this.captchaAttempts += 1;
}
const { action, fields } = parseFormField($);
const src = $('.captcha_img').attr('src');
const { key, validate } = await this.vk.callbackService.processingCaptcha({
type: captchaTypes.ACCOUNT_VERIFICATION,
sid: fields.captcha_sid,
src
});
this.captchaValidate = validate;
fields.captcha_key = key;
const url = getFullURL(action, response);
url.searchParams.set('utf8', 1);
const pageResponse = await this.fetch(url, {
method: 'POST',
body: new URLSearchParams(fields)
});
return pageResponse;
}
}
function sequential(next) {
this.callMethod(this.queue.shift());
next();
}
async function parallel(next) {
const { queue } = this;
if (queue[0].method.startsWith('execute')) {
sequential.call(this, next);
return;
}
// Wait next event loop, saves one request or more
await delay(0);
const { apiExecuteCount } = this.vk.options;
const tasks = [];
const chain = [];
for (let i = 0; i < queue.length; i += 1) {
if (queue[i].method.startsWith('execute')) {
continue;
}
const request = queue.splice(i, 1)[0];
i -= 1;
tasks.push(request);
chain.push(String(request));
if (tasks.length >= apiExecuteCount) {
break;
}
}
try {
const request = new Request('execute', {
code: getChainReturn(chain)
});
this.callMethod(request);
next();
resolveExecuteTask(tasks, await request.promise);
} catch (error) {
for (const task of tasks) {
task.reject(error);
}
}
}
async function parallelSelected(next) {
const { apiExecuteMethods, apiExecuteCount } = this.vk.options;
const { queue } = this;
if (!apiExecuteMethods.includes(queue[0].method)) {
sequential.call(this, next);
return;
}
// Wait next event loop, saves one request or more
await delay(0);
const tasks = [];
const chain = [];
for (let i = 0; i < queue.length; i += 1) {
if (!apiExecuteMethods.includes(queue[i].method)) {
continue;
}
const request = queue.splice(i, 1)[0];
i -= 1;
tasks.push(request);
chain.push(String(request));
if (tasks.length >= apiExecuteCount) {
break;
}
}
if (tasks.length === 0) {
sequential.call(this, next);
return;
}
try {
const request = new Request('execute', {
code: getChainReturn(chain)
});
this.callMethod(request);
next();
resolveExecuteTask(tasks, await request.promise);
} catch (error) {
for (const task of tasks) {
task.reject(error);
}
}
}
const { inspect: inspect$1 } = nodeUtil__default;
const { URLSearchParams: URLSearchParams$1 } = nodeUrl;
const {
CAPTCHA_REQUIRED: CAPTCHA_REQUIRED$1,
TOO_MANY_REQUESTS,
USER_VALIDATION_REQUIRED: USER_VALIDATION_REQUIRED$1
} = apiErrors;
const debug$2 = createDebug('vk-io:api');
const requestHandlers = {
sequential,
parallel,
parallel_selected: parallelSelected
};
/**
* Returns request handler
*
* @param {string} mode
*
* @return {Function}
*/
const getRequestHandler = (mode = 'sequential') => {
const handler = requestHandlers[mode];
if (!handler) {
throw new VKError({
message: 'Unsuported api mode'
});
}
return handler;
};
const groupMethods = [
'account',
'ads',
'appWidgets',
'apps',
'audio',
'auth',
'board',
'database',
'docs',
'fave',
'friends',
'gifts',
'groups',
'leads',
'leadForms',
'likes',
'market',
'messages',
'newsfeed',
'notes',
'notifications',
'orders',
'pages',
'photos',
'places',
'polls',
'podcasts',
'prettyCards',
'search',
'secure',
'stats',
'status',
'storage',
'stories',
'streaming',
'users',
'utils',
'video',
'wall',
'widgets'
];
/**
* Working with API methods
*
* @public
*/
class API {
/**
* Constructor
*
* @param {VK} vk
*/
constructor(vk) {
this.vk = vk;
this.queue = [];
this.started = false;
this.suspended = false;
for (const group of groupMethods) {
const isMessagesGroup = group === 'messages';
/**
* NOTE: Optimization for other methods
*
* Instead of checking everywhere the presence of a property in an object
* The check is only for the messages group
* Since it is necessary to change the behavior of the sending method
*/
this[group] = new Proxy(
isMessagesGroup
? {
send: (params = {}) => {
if (!('random_id' in params)) {
params = {
...params,
random_id: getRandomId()
};
}
return this.enqueue('messages.send', params);
}
}
: {},
{
get: isMessagesGroup
? (obj, prop) => obj[prop] || (
params => (
this.enqueue(`${group}.${prop}`, params)
)
)
: (obj, prop) => params => (
this.enqueue(`${group}.${prop}`, params)
)
}
);
}
}
/**
* Returns custom tag
*
* @return {string}
*/
get [Symbol.toStringTag]() {
return 'API';
}
/**
* Returns the current used API version
*
* @return {string}
*/
get API_VERSION() {
return API_VERSION;
}
/**
* Call execute method
*
* @param {Object} params
*
* @return {Promise<Object>}
*/
execute(params) {
return this.enqueue('execute', params);
}
/**
* Call execute procedure
*
* @param {string} name
* @param {Object} params
*
* @return {Promise<Object>}
*/
procedure(name, params) {
return this.enqueue(`execute.${name}`, params);
}
/**
* Call raw method
*
* @param {string} method
* @param {Object} params
*
* @return {Promise<Object>}
*/
call(method, params) {
return this.enqueue(method, params);
}
/**
* Adds request for queue
*
* @param {Request} request
*
* @return {Promise<Object>}
*/
callWithRequest(request) {
this.queue.push(request);
this.worker();
return request.promise;
}
/**
* Adds method to queue
*
* @param {string} method
* @param {Object} params
*
* @return {Promise<Object>}
*/
enqueue(method, params) {
const request = new Request(method, params);
return this.callWithRequest(request);
}
/**
* Adds an element to the beginning of the queue
*
* @param {Request} request
*/
requeue(request) {
this.queue.unshift(request);
this.worker();
}
/**
* Running queue
*/
worker() {
if (this.started) {
return;
}
this.started = true;
const { apiLimit, apiMode } = this.vk.options;
const handler = getRequestHandler(apiMode);
const interval = Math.round(MINIMUM_TIME_INTERVAL_API / apiLimit);
const work = () => {
if (this.queue.length === 0 || this.suspended) {
this.started = false;
return;
}
handler.call(this, () => {
setTimeout(work, interval);
});
};
work();
}
/**
* Calls the api method
*
* @param {Request} request
*/
async callMethod(request) {
const { options } = this.vk;
const { method } = request;
const params = {
access_token: options.token,
v: API_VERSION,
...request.params
};
if (options.language !== null) {
params.lang = options.language;
}
debug$2(`http --> ${method}`);
const startTime = Date.now();
let response;
try {
response = await fetch(`${options.apiBaseUrl}/${method}`, {
method: 'POST',
compress: false,
agent: options.agent,
timeout: options.apiTimeout,
headers: {
...options.apiHeaders,
connection: 'keep-alive'
},
body: new URLSearchParams$1(params)
});
response = await response.json();
} catch (error) {
if (request.addAttempt() <= options.apiAttempts) {
await delay(options.apiWait);
debug$2(`Request ${method} restarted ${request.attempts} times`);
this.requeue(request);
return;
}
if ('captchaValidate' in request) {
request.captchaValidate.reject(error);
}
request.reject(error);
return;
}
const endTime = (Date.now() - startTime).toLocaleString();
debug$2(`http <-- ${method} ${endTime}ms`);
if ('error' in response) {
this.handleError(request, new APIError(response.error));
return;
}
if ('captchaValidate' in request) {
request.captchaValidate.resolve();
}
if (method.startsWith('execute')) {
request.resolve({
response: response.response,
errors: (response.execute_errors || []).map(error => (
new ExecuteError(error)
))
});
return;
}
request.resolve(
response.response !== undefined
? response.response
: response
);
}
/**
* Error API handler
*
* @param {Request} request
* @param {Object} error
*/
async handleError(request, error) {
const { code } = error;
if (code === TOO_MANY_REQUESTS) {
if (this.suspended) {
this.requeue(request);
return;
}
this.suspended = true;
await delay((MINIMUM_TIME_INTERVAL_API / this.vk.options.apiLimit) + 50);
this.suspended = false;
this.requeue(request);
return;
}
if ('captchaValidate' in request) {
request.captchaValidate.reject(error);
}
if (code === USER_VALIDATION_REQUIRED$1) {
if (this.suspended) {
this.requeue(request);
}
this.suspended = true;
try {
const verification = new AccountVerification(this.vk);
const { token } = await verification.run(error.redirectUri);
debug$2('Account verification passed');
this.vk.token = token;
this.suspended = false;
this.requeue(request);
} catch (verificationError) {
debug$2('Account verification error', verificationError);
request.reject(error);
await delay(15e3);
this.suspended = false;
this.worker();
}
return;
}
if (code !== CAPTCHA_REQUIRED$1 || !this.vk.callbackService.hasCaptchaHandler) {
request.reject(error);
return;
}
try {
const { captchaSid } = error;
const { key, validate } = await this.vk.callbackService.processingCaptcha({
type: captchaTypes.API,
src: error.captchaImg,
sid: captchaSid,
request
});
request.captchaValidate = validate;
request.params.captcha_sid = captchaSid;
request.params.captcha_key = key;
this.requeue(request);
} catch (e) {
request.reject(e);
}
}
/**
* Custom inspect object
*
* @param {?number} depth
* @param {Object} options
*
* @return {string}
*/
[inspect$1.custom](depth, options) {
const { name } = this.constructor;
const { started, queue } = this;
const payload = { started, queue };
return `${options.stylize(name, 'special')} ${inspect$1(payload, options)}`;
}
}
const { load: cheerioLoad$1 } = cheerio;
const { URL: URL$2, URLSearchParams: URLSearchParams$2 } = nodeUrl;
const debug$3 = createDebug('vk-io:auth:direct');
const {
INVALID_PHONE_NUMBER: INVALID_PHONE_NUMBER$1,
AUTHORIZATION_FAILED: AUTHORIZATION_FAILED$1,
FAILED_PASSED_CAPTCHA: FAILED_PASSED_CAPTCHA$1,
FAILED_PASSED_TWO_FACTOR: FAILED_PASSED_TWO_FACTOR$1
} = authErrors;
/**
* Number of two-factorial attempts
*
* @type {number}
*/
const TWO_FACTOR_ATTEMPTS$1 = 3;
/**
* Number of captcha attempts
*
* @type {number}
*/
const CAPTCHA_ATTEMPTS = 3;
/**
* Phone number check action
*
* @type {string}
*/
const ACTION_SECURITY_CODE$1 = 'act=security';
class DirectAuth {
/**
* Constructor
*
* @param {VK} vk
* @param {Object} options
*/
constructor(vk, {
appId = vk.options.appId,
appSecret = vk.options.appSecret,
login = vk.options.login,
phone = vk.options.phone,
password = vk.options.password,
scope = vk.options.authScope,
agent = vk.options.agent,
timeout = vk.options.authTimeout
} = {}) {
this.vk = vk;
this.appId = appId;
this.appSecret = appSecret;
this.login = login;
this.phone = phone;
this.password = password;
this.agent = agent;
this.scope = scope;
this.timeout = timeout;
this.started = false;
this.captchaValidate = null;
this.captchaAttempts = 0;
this.twoFactorValidate = null;
this.twoFactorAttempts = 0;
}
/**
* Returns custom tag
*
* @return {string}
*/
get [Symbol.toStringTag]() {
return 'DirectAuth';
}
/**
* Executes the HTTP request
*
* @param {string} url
* @param {Object} options
*
* @return {Promise<Response>}
*/
fetch(url, options = {}) {
const { agent, timeout } = this;
const { headers = {} } = options;
return this.fetchCookie(url, {
...options,
agent,
timeout,
compress: false,
headers: {
...headers,
'User-Agent': DESKTOP_USER_AGENT
}
});
}
/**
* Returns permission page
*
* @param {Object} query
*
* @return {Response}
*/
getPermissionsPage(query = {}) {
let { scope } = this;
if (scope === 'all' || scope === null) {
scope = getAllUsersPermissions();
} else if (typeof scope !== 'number') {
scope = getUsersPermissionsByName(scope);
}
debug$3('auth scope %s', scope);
const {
appId,
appSecret,
login,
phone,
password
} = this;
const params = new URLSearchParams$2({
...query,
username: login || phone,
grant_type: 'password',
client_secret: appSecret,
'2fa_supported': this.vk.callbackService.hasTwoFactorHandler
? 1
: 0,
v: API_VERSION,
client_id: appId,
password,
scope
});
const url = new URL$2(`https://oauth.vk.com/token?${params}`);
return this.fetch(url, {
method: 'GET'
});
}
/**
* Runs authorization
*
* @return {Promise<Object>}
*/
// eslint-disable-next-line consistent-return
async run() {
if (this.started) {
throw new AuthError({
message: 'Authorization already started!',
code: AUTHORIZATION_FAILED$1
});
}
this.started = true;
this.fetchCookie = fetchCookieFollowRedirectsDecorator();
let response = await this.getPermissionsPage();
let text;
const isProcessed = true;
while (isProcessed) {
text = await response.text();
let isJSON = true;
try {
text = JSON.parse(text);
} catch (e) {
isJSON = false;
}
if (isJSON) {
if ('access_token' in text) {
const {
email = null,
user_id: user = null,
expires_in: expires = null,
access_token: token,
} = text;
return {
email,
user: user !== null
? Number(user)
: null,
token,
expires: expires !== null
? Number(expires)
: null
};
}
if ('error' in text) {
if (text.error === 'invalid_client') {
throw new AuthError({
message: `Invalid client (${text.error_description})`,
code: AUTHORIZATION_FAILED$1
});
}
if (text.error === 'need_captcha') {
response = await this.processCaptcha(text);
continue;
}
if (text.error === 'need_validation') {
if ('validation_type' in text) {
response = await this.processTwoFactor(text);
continue;
}
const $ = cheerioLoad$1(text);
response = this.processSecurityForm(response, $);
continue;
}
throw new AuthError({
message: 'Unsupported type validation',
code: AUTHORIZATION_FAILED$1
});
}
}
throw new AuthError({
message: 'Authorization failed',
code: AUTHORIZATION_FAILED$1
});
}
}
/**
* Process captcha
*
* @param {Object} payload
*
* @return {Response}
*/
async processCaptcha({ captcha_sid: sid, captcha_img: src }) {
debug$3('captcha process');
if (this.captchaValidate !== null) {
this.captchaValidate.reject(new AuthError({
message: 'Incorrect captcha code',
code: FAILED_PASSED_CAPTCHA$1
}));
this.captchaValidate = null;
this.captchaAttempts += 1;
}
if (this.captchaAttempts >= CAPTCHA_ATTEMPTS) {
throw new AuthError({
message: 'Maximum attempts passage captcha',
code: FAILED_PASSED_CAPTCHA$1
});
}
const { key, validate } = await this.vk.callbackService.processingCaptcha({
type: captchaTypes.DIRECT_AUTH,
sid,
src
});
this.captchaValidate = validate;
const response = await this.getPermissionsPage({
captcha_sid: sid,
captcha_key: key
});
return response;
}
/**
* Process two-factor
*
* @param {Object} response
*
* @return {Promise<Response>}
*/
async processTwoFactor({ validation_type: validationType, phone_mask: phoneMask }) {
debug$3('process two-factor handle');
if (this.twoFactorValidate !== null) {
this.twoFactorValidate.reject(new AuthError({
message: 'Incorrect two-factor code',
code: FAILED_PASSED_TWO_FACTOR$1
}));
this.twoFactorValidate = null;
this.twoFactorAttempts += 1;
}
if (this.twoFactorAttempts >= TWO_FACTOR_ATTEMPTS$1) {
throw new AuthError({
message: 'Failed passed two-factor authentication',
code: FAILED_PASSED_TWO_FACTOR$1
});
}
const { code, validate } = await this.vk.callbackService.processingTwoFactor({
phoneMask,
type: validationType === '2fa_app'
? 'app'
: 'sms'
});
this.twoFactorValidate = validate;
const response = await this.getPermissionsPage({ code });
return response;
}
/**
* Process security form
*
* @param {Response} response
* @param {Cheerio} $
*
* @return {Promise<Response>}
*/
async processSecurityForm(response, $) {
debug$3('process security form');
const { login, phone } = this;
let number;
if (phone !== null) {
number = phone;
} else if (login !== null && !login.includes('@')) {
number = login;
} else {
throw new AuthError({
message: 'Missing phone number in the phone or login field',
code: INVALID_PHONE_NUMBER$1
});
}
if (typeof number === 'string') {
number = number.trim().replace(/^(\+|00)/, '');
}
number = String(number);
const $field = $('.field_prefix');
const prefix = $field.first().text().trim().replace('+', '').length;
const postfix = $field.last().text().trim().length;
const { action, fields } = parseFormField($);
fields.code = number.slice(prefix, number.length - postfix);
const url = getFullURL(action, response);
response = await this.fetch(url, {
method: 'POST',
body: new URLSearchParams$2(fields)
});
if (response.url.includes(ACTION_SECURITY_CODE$1)) {
throw new AuthError({
message: 'Invalid phone number',
code: INVALID_PHONE_NUMBER$1
});
}
return response;
}
}
const { load: cheerioLoad$2 } = cheerio;
const { URL: URL$3, URLSearchParams: URLSearchParams$3 } = nodeUrl;
const { promisify: promisify$1 } = nodeUtil__default;
const debug$4 = createDebug('vk-io:auth:implicit-flow');
const {
PAGE_BLOCKED,
INVALID_PHONE_NUMBER: INVALID_PHONE_NUMBER$2,
AUTHORIZATION_FAILED: AUTHORIZATION_FAILED$2,
FAILED_PASSED_CAPTCHA: FAILED_PASSED_CAPTCHA$2,
FAILED_PASSED_TWO_FACTOR: FAILED_PASSED_TWO_FACTOR$2
} = authErrors;
/**
* Blocked action
*
* @type {string}
*/
const ACTION_BLOCKED = 'act=blocked';
/**
* Two-factor auth check action
*
* @type {string}
*/
const ACTION_AUTH_CODE$1 = 'act=authcheck';
/**
* Phone number check action
*
* @type {string}
*/
const ACTION_SECURITY_CODE$2 = 'act=security';
/**
* Number of two-factorial attempts
*
* @type {number}
*/
const TWO_FACTOR_ATTEMPTS$2 = 3;
/**
* Number of captcha attempts
*
* @type {number}
*/
const CAPTCHA_ATTEMPTS$1 = 3;
/**
* Removes the prefix
*
* @type {RegExp}
*/
const REPLACE_PREFIX_RE = /^[+|0]+/;
/**
* Find location.href text
*
* @type {RegExp}
*/
const FIND_LOCATION_HREF_RE = /location\.href\s+=\s+"([^"]+)"/i;
class ImplicitFlow {
/**
* Constructor
*
* @param {VK} vk
* @param {Object} options
*/
constructor(vk, {
appId = vk.options.appId,
appSecret = vk.options.appSecret,
login = vk.options.login,
phone = vk.options.phone,
password = vk.options.password,
agent = vk.options.agent,
scope = vk.options.authScope,
timeout = vk.options.authTimeout
} = {}) {
this.vk = vk;
this.appId = appId;
this.appSecret = appSecret;
this.login = login;
this.phone = phone;
this.password = password;
this.agent = agent;
this.scope = scope;
this.timeout = timeout;
this.jar = new CookieJar();
this.started = false;
this.captchaValidate = null;
this.captchaAttempts = 0;
this.twoFactorValidate = null;
this.twoFactorAttempts = 0;
}
/**
* Returns custom tag
*
* @return {string}
*/
get [Symbol.toStringTag]() {
return this.constructor.name;
}
/**
* Returns CookieJar
*
* @return {CookieJar}
*/
get cookieJar() {
return this.jar;
}
/**
* Sets the CookieJar
*
* @param {CookieJar} jar
*
* @return {this}
*/
set cookieJar(jar) {
this.jar = jar;
}
/**
* Returns cookie
*
* @return {Promise<Object>}
*/
async getCookies() {
const { jar } = this;
const getCookieString = promisify$1(jar.getCookieString).bind(jar);
const [login, main] = await Promise.all([
getCookieString('https://login.vk.com'),
getCookieString('https://vk.com')
]);
return {
'login.vk.com': login,
'vk.com': main
};
}
/**
* Executes the HTTP request
*
* @param {string} url
* @param {Object} options
*
* @return {Promise<Response>}
*/
fetch(url, options = {}) {
const { agent, timeout } = this;
const { headers = {} } = options;
return this.fetchCookie(url, {
...options,
agent,
timeout,
compress: false,
headers: {
...headers,
'User-Agent': DESKTOP_USER_AGENT
}
});
}
/**
* Runs authorization
*
* @return {Promise<Object>}
*/
// eslint-disable-next-line consistent-return
async run() {
if (this.started) {
throw new AuthError({
message: 'Authorization already started!',
code: AUTHORIZATION_FAILED$2
});
}
this.started = true;
this.fetchCookie = fetchCookieFollowRedirectsDecorator(this.jar);
debug$4('get permissions page');
let response = await this.getPermissionsPage();
const isProcessed = true;
while (isProcessed) {
const { url } = response;
debug$4('URL', url);
if (url.includes(CALLBACK_BLANK)) {
return { response };
}
if (url.includes(ACTION_BLOCKED)) {
debug$4('page blocked');
throw new AuthError({
message: 'Page blocked',
code: PAGE_BLOCKED
});
}
const $ = cheerioLoad$2(await response.text());
if (url.includes(ACTION_AUTH_CODE$1)) {
response = await this.processTwoFactorForm(response, $);
continue;
}
if (url.includes(ACTION_SECURITY_CODE$2)) {
response = await this.processSecurityForm(response, $);
continue;
}
const $error = $('.box_error');
const $service = $('.service_msg_warning');
const isError = $error.length !== 0;
if (this.captchaValidate === null && (isError || $service.length !== 0)) {
const errorText = isError
? $error.text()
: $service.text();
throw new AuthError({
message: `Auth form error: ${errorText}`,
code: AUTHORIZATION_FAILED$2,
pageHtml: $.html()
});
}
if ($('input[name="pass"]').length !== 0) {
response = await this.processAuthForm(response, $);
continue;
}
if (url.includes('act=')) {
throw new AuthError({
message: 'Unsupported authorization event',
code: AUTHORIZATION_FAILED$2,
pageHtml: $.html()
});
}
debug$4('auth with login & pass complete');
if ($('form').length !== 0) {
const { action } = parseFormField($);
debug$4('url grant access', action);
response = await this.fetch(action, {
method: 'POST'
});
} else {
const locations = $.html().match(FIND_LOCATION_HREF_RE);
if (locations === null) {
throw new AuthError({
message: 'Could not log in',
code: AUTHORIZATION_FAILED$2,
pageHtml: $.html()
});
}
const location = locations[1].replace('&cancel=1', '');
debug$4('url grant access', location);
response = await this.fetch(location, {
method: 'POST'
});
}
}
}
/**
* Process form auth
*
* @param {Response} response
* @param {Cheerio} $
*
* @return {Promise<Response>}
*/
async processAuthForm(response, $) {
debug$4('process login handle');
if (this.captchaValidate !== null) {
this.captchaValidate.reject(new AuthError({
message: 'Incorrect captcha code',
code: FAILED_PASSED_CAPTCHA$2,
pageHtml: $.html()
}));
this.captchaValidate = null;
this.captchaAttempts += 1;
}
if (this.captchaAttempts > CAPTCHA_ATTEMPTS$1) {
throw new AuthError({
message: 'Maximum attempts passage captcha',
code: FAILED_PASSED_CAPTCHA$2
});
}
const { login, password, phone } = this;
const { action, fields } = parseFormField($);
fields.email = login || phone;
fields.pass = password;
if ('captcha_sid' in fields) {
const src = $('.oauth_captcha').attr('src') || $('#captcha').attr('src');
const { key, validate } = await this.vk.callbackService.processingCaptcha({
type: captchaTypes.IMPLICIT_FLOW_AUTH,
sid: fields.captcha