From b14c1b203df3b02f265663cc5c90ca3e9b9ae357 Mon Sep 17 00:00:00 2001 From: Konstantin Bubyakin Date: Sun, 15 Mar 2020 23:38:03 +0300 Subject: [PATCH 1/3] =?UTF-8?q?refactor(vk):=20=D0=B2=D1=8B=D0=BD=D0=B5?= =?UTF-8?q?=D1=81=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D1=80=D0=B5=D1=88=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BA=D0=B0=D0=BF?= =?UTF-8?q?=D1=87=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../taskConsumer/actions/vk/captcha-solver.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 services/taskConsumer/actions/vk/captcha-solver.ts diff --git a/services/taskConsumer/actions/vk/captcha-solver.ts b/services/taskConsumer/actions/vk/captcha-solver.ts new file mode 100644 index 00000000..49bf3922 --- /dev/null +++ b/services/taskConsumer/actions/vk/captcha-solver.ts @@ -0,0 +1,45 @@ +import { inject, injectable } from 'inversify'; +import { Page } from 'puppeteer'; +import { CaptchaService } from '../../../../lib/captcha.service'; + +@injectable() +export class CaptchaSolver { + constructor(@inject(CaptchaService) private readonly captcha: CaptchaService) {} + + async solveIfHas(page: Page) { + const hasCaptcha = await page.evaluate(() => !!document.querySelector('.recaptcha iframe')); + if (!hasCaptcha) { + return; + } + + try { + const captchaUrl = await page.evaluate(() => + document.querySelector('.recaptcha iframe').getAttribute('src'), + ); + const pageUrl = await page.evaluate(() => document.location.href); + const urlObject = new URL(captchaUrl); + const siteKey = urlObject.searchParams.get('k'); + const result = await this.captcha.solveRecaptchaV2({ + pageUrl, + siteKey, + }); + const captchaNavigationPromise = page.waitForNavigation(); + await page.evaluate( + token => { + document.querySelector( + '.recaptcha .g-recaptcha-response', + ).value = token; + document.querySelector('#quick_recaptcha').value = token; + document.querySelector('#quick_login_form').submit(); + }, + result, + siteKey, + ); + await captchaNavigationPromise; + } catch (error) { + error.code = 'captcha_failed'; + error.canRetry = true; + throw error; + } + } +} From d26ade1d405cb9313de51da3c2eca278a9789213 Mon Sep 17 00:00:00 2001 From: Konstantin Bubyakin Date: Sun, 15 Mar 2020 23:38:34 +0300 Subject: [PATCH 2/3] =?UTF-8?q?refactor(vk):=20=D0=B2=D1=8B=D0=BD=D0=B5?= =?UTF-8?q?=D1=81=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=BF=D0=BB=D0=B5=D0=BA=D1=81=D0=BD=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D0=BE=D0=B6=D0=B8=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=B4=D0=B5=D0=B9=D1=81=D1=82=D0=B2=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../taskConsumer/actions/vk/action-applier.ts | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 services/taskConsumer/actions/vk/action-applier.ts diff --git a/services/taskConsumer/actions/vk/action-applier.ts b/services/taskConsumer/actions/vk/action-applier.ts new file mode 100644 index 00000000..5ada3ab2 --- /dev/null +++ b/services/taskConsumer/actions/vk/action-applier.ts @@ -0,0 +1,73 @@ +import { inject, injectable } from 'inversify'; +import { Page } from 'puppeteer'; +import bluebird from 'bluebird'; +import { CaptchaSolver } from './captcha-solver'; +import { AccountException } from '../../rpc-handlers/account.exception'; + +export type ActionArgs = { + page: Page; + goalAction: Function; + login: string; +}; + +export type ClickActionArgs = ActionArgs & { + selector: string; +}; + +export type CallbackAction = ActionArgs & { + callback: Function; +}; + +@injectable() +export class ActionApplier { + constructor(@inject(CaptchaSolver) private readonly captchaSolver: CaptchaSolver) {} + + async click({ page, goalAction, selector, login }: ClickActionArgs) { + return this.callback({ + callback: () => { + return page.evaluate(selectorForClick => { + document.querySelector(selectorForClick).click(); + }, selector); + }, + page, + login, + goalAction, + }); + } + + async callback({ callback, page, goalAction, login }: CallbackAction) { + const waitForCaptchaPromise = page.waitFor( + () => !!document.querySelector('.recaptcha iframe'), + { timeout: 10000 }, + ); + + const waitForPhoneConfirmation = page.waitForFunction( + () => !!document.querySelector('#validation_phone_row'), + { timeout: 10000 }, + ); + + const result = await callback(); + + await bluebird.any([waitForCaptchaPromise, goalAction(), waitForPhoneConfirmation]); + try { + await this.captchaSolver.solveIfHas(page); + } catch (error) { + error.login = login; + throw error; + } + const needPhoneConfirmation = await page.evaluate( + () => !!document.querySelector('#validation_phone_row'), + ); + + if (needPhoneConfirmation) { + throw new AccountException( + 'Account requires phone confirmation', + 'phone_required', + login, + false, + ); + } + + return result; + } +} From f27e27816c76100ae5dd9adedebc122061bcd5c7 Mon Sep 17 00:00:00 2001 From: Konstantin Bubyakin Date: Sun, 15 Mar 2020 23:39:18 +0300 Subject: [PATCH 3/3] =?UTF-8?q?refactor(vk):=20=D0=B8=D0=BD=D1=82=D0=B5?= =?UTF-8?q?=D0=B3=D1=80=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BE=D0=B6=D0=B8=D0=B4?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D0=B0=D0=B2=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8E=20=D0=B8=20=D0=B2?= =?UTF-8?q?=D1=81=D1=82=D1=83=D0=BF=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../taskConsumer/actions/vk/vk-authorizer.ts | 60 ++++------------ .../rpc-handlers/join-group-rpc.handler.ts | 70 ++++++++----------- 2 files changed, 41 insertions(+), 89 deletions(-) diff --git a/services/taskConsumer/actions/vk/vk-authorizer.ts b/services/taskConsumer/actions/vk/vk-authorizer.ts index 30020f5d..d2c3b22d 100644 --- a/services/taskConsumer/actions/vk/vk-authorizer.ts +++ b/services/taskConsumer/actions/vk/vk-authorizer.ts @@ -1,16 +1,16 @@ import { inject, injectable } from 'inversify'; import { Page } from 'puppeteer'; -import bluebird, { AggregateError } from 'bluebird'; +import { AggregateError } from 'bluebird'; import { LoggerInterface } from '../../../../lib/logger.interface'; import { ProxyInterface } from '../../proxy.interface'; import { AccountException } from '../../rpc-handlers/account.exception'; -import { CaptchaService } from '../../../../lib/captcha.service'; +import { ActionApplier } from './action-applier'; @injectable() export class VkAuthorizer { constructor( @inject('Logger') private readonly logger: LoggerInterface, - @inject(CaptchaService) private readonly captcha: CaptchaService, + @inject(ActionApplier) private readonly actionApplier: ActionApplier, ) {} async signInWithCookie(page: Page, login: string, remixsid: string): Promise { @@ -66,53 +66,19 @@ export class VkAuthorizer { password, ); - const loginNavigationPromise = page.waitForNavigation({ timeout: 10000 }); - const waitForCaptchaPromise = page.waitFor( - () => !!document.querySelector('.recaptcha iframe'), - { timeout: 10000 }, - ); - await page.click('#login_button'); - await bluebird - .any([loginNavigationPromise as Promise, waitForCaptchaPromise as Promise]) - .catch(async error => { - if (error instanceof AggregateError) { - return page.reload({ waitUntil: 'networkidle2' }); - } - - throw error; + try { + await this.actionApplier.click({ + page, + goalAction: () => page.waitForNavigation({ timeout: 10000 }), + selector: '#login_button', + login, }); - - const hasCaptcha = await page.evaluate(() => !!document.querySelector('.recaptcha iframe')); - if (hasCaptcha) { - try { - const captchaUrl = await page.evaluate(() => - document.querySelector('.recaptcha iframe').getAttribute('src'), - ); - const urlObject = new URL(captchaUrl); - const siteKey = urlObject.searchParams.get('k'); - const result = await this.captcha.solveRecaptchaV2({ - pageUrl: 'https://vk.com/login', - siteKey, - }); - const captchaNavigationPromise = page.waitForNavigation(); - await page.evaluate( - token => { - document.querySelector( - '.recaptcha .g-recaptcha-response', - ).value = token; - document.querySelector('#quick_recaptcha').value = token; - document.querySelector('#quick_login_form').submit(); - }, - result, - siteKey, - ); - await captchaNavigationPromise; - } catch (error) { - error.code = 'captcha_failed'; - error.login = login; - error.canRetry = true; + } catch (error) { + if (!(error instanceof AggregateError)) { throw error; } + + await page.reload({ waitUntil: 'networkidle2' }); } await this.checkAccount(page, login); diff --git a/services/taskConsumer/rpc-handlers/join-group-rpc.handler.ts b/services/taskConsumer/rpc-handlers/join-group-rpc.handler.ts index 4f5257aa..3f270907 100644 --- a/services/taskConsumer/rpc-handlers/join-group-rpc.handler.ts +++ b/services/taskConsumer/rpc-handlers/join-group-rpc.handler.ts @@ -7,7 +7,7 @@ import { VkUserCredentialsInterface } from '../../api/vk-users/vk-user-credentia import { createBrowserPage } from '../actions/create-page'; import { hrefByGroupId } from '../../../lib/helper'; import { JoinGroupFailedException } from './join-group-failed.exception'; -import { AccountException } from './account.exception'; +import { ActionApplier } from '../actions/vk/action-applier'; type TaskArgsType = { userCredentials: VkUserCredentialsInterface; @@ -20,6 +20,8 @@ export class JoinGroupRpcHandler extends AbstractRpcHandler { @inject(VkAuthorizer) private readonly vkAuthorizer: VkAuthorizer; + @inject(ActionApplier) private readonly actionApplier: ActionApplier; + protected readonly method = 'joinGroup'; static readonly method = 'joinGroup'; @@ -63,22 +65,31 @@ export class JoinGroupRpcHandler extends AbstractRpcHandler { }); } - const subscribeClicked = await page.evaluate(() => { - const subscribeButton = document.querySelector( - '#public_subscribe', - ); - const joinButton = document.querySelector('#join_button'); - if (subscribeButton) { - subscribeButton.click(); - return true; - } - - if (joinButton) { - joinButton.click(); - return true; - } - - return false; + const subscribeClicked = await this.actionApplier.callback({ + callback: () => { + return page.evaluate(() => { + const subscribeButton = document.querySelector( + '#public_subscribe', + ); + const joinButton = document.querySelector( + '#join_button', + ); + if (subscribeButton) { + subscribeButton.click(); + return true; + } + + if (joinButton) { + joinButton.click(); + return true; + } + + return false; + }); + }, + login: userCredentials.login, + goalAction: () => page.waitForSelector('#page_actions_btn'), + page, }); if (!subscribeClicked) { @@ -90,31 +101,6 @@ export class JoinGroupRpcHandler extends AbstractRpcHandler { ); } - await page.waitForFunction(() => { - const form = document.querySelector('#validation_phone_row'); - if (form) { - return true; - } - - return !!document.querySelector('#page_actions_btn'); - }); - - const needPhoneConfirmation = await page.evaluate(() => { - const form = document.querySelector('#validation_phone_row'); - return !!form; - }); - - if (needPhoneConfirmation) { - throw new AccountException( - 'Account requires phone confirmation', - 'phone_required', - userCredentials.login, - false, - ); - } - - await page.waitForSelector('#page_actions_btn'); - this.logger.info({ message: 'Подписался в группу', credentials: userCredentials,