Skip to content

Commit 636684c

Browse files
authored
Merge pull request #158 from Goodluckhf/capthca-fix
Capthca fix
2 parents 9010489 + f27e278 commit 636684c

4 files changed

Lines changed: 159 additions & 89 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { inject, injectable } from 'inversify';
2+
import { Page } from 'puppeteer';
3+
import bluebird from 'bluebird';
4+
import { CaptchaSolver } from './captcha-solver';
5+
import { AccountException } from '../../rpc-handlers/account.exception';
6+
7+
export type ActionArgs = {
8+
page: Page;
9+
goalAction: Function;
10+
login: string;
11+
};
12+
13+
export type ClickActionArgs = ActionArgs & {
14+
selector: string;
15+
};
16+
17+
export type CallbackAction = ActionArgs & {
18+
callback: Function;
19+
};
20+
21+
@injectable()
22+
export class ActionApplier {
23+
constructor(@inject(CaptchaSolver) private readonly captchaSolver: CaptchaSolver) {}
24+
25+
async click({ page, goalAction, selector, login }: ClickActionArgs) {
26+
return this.callback({
27+
callback: () => {
28+
return page.evaluate(selectorForClick => {
29+
document.querySelector<HTMLButtonElement>(selectorForClick).click();
30+
}, selector);
31+
},
32+
page,
33+
login,
34+
goalAction,
35+
});
36+
}
37+
38+
async callback({ callback, page, goalAction, login }: CallbackAction) {
39+
const waitForCaptchaPromise = page.waitFor(
40+
() => !!document.querySelector('.recaptcha iframe'),
41+
{ timeout: 10000 },
42+
);
43+
44+
const waitForPhoneConfirmation = page.waitForFunction(
45+
() => !!document.querySelector('#validation_phone_row'),
46+
{ timeout: 10000 },
47+
);
48+
49+
const result = await callback();
50+
51+
await bluebird.any([waitForCaptchaPromise, goalAction(), waitForPhoneConfirmation]);
52+
try {
53+
await this.captchaSolver.solveIfHas(page);
54+
} catch (error) {
55+
error.login = login;
56+
throw error;
57+
}
58+
const needPhoneConfirmation = await page.evaluate(
59+
() => !!document.querySelector('#validation_phone_row'),
60+
);
61+
62+
if (needPhoneConfirmation) {
63+
throw new AccountException(
64+
'Account requires phone confirmation',
65+
'phone_required',
66+
login,
67+
false,
68+
);
69+
}
70+
71+
return result;
72+
}
73+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { inject, injectable } from 'inversify';
2+
import { Page } from 'puppeteer';
3+
import { CaptchaService } from '../../../../lib/captcha.service';
4+
5+
@injectable()
6+
export class CaptchaSolver {
7+
constructor(@inject(CaptchaService) private readonly captcha: CaptchaService) {}
8+
9+
async solveIfHas(page: Page) {
10+
const hasCaptcha = await page.evaluate(() => !!document.querySelector('.recaptcha iframe'));
11+
if (!hasCaptcha) {
12+
return;
13+
}
14+
15+
try {
16+
const captchaUrl = await page.evaluate(() =>
17+
document.querySelector('.recaptcha iframe').getAttribute('src'),
18+
);
19+
const pageUrl = await page.evaluate(() => document.location.href);
20+
const urlObject = new URL(captchaUrl);
21+
const siteKey = urlObject.searchParams.get('k');
22+
const result = await this.captcha.solveRecaptchaV2({
23+
pageUrl,
24+
siteKey,
25+
});
26+
const captchaNavigationPromise = page.waitForNavigation();
27+
await page.evaluate(
28+
token => {
29+
document.querySelector<HTMLInputElement>(
30+
'.recaptcha .g-recaptcha-response',
31+
).value = token;
32+
document.querySelector<HTMLInputElement>('#quick_recaptcha').value = token;
33+
document.querySelector<HTMLFormElement>('#quick_login_form').submit();
34+
},
35+
result,
36+
siteKey,
37+
);
38+
await captchaNavigationPromise;
39+
} catch (error) {
40+
error.code = 'captcha_failed';
41+
error.canRetry = true;
42+
throw error;
43+
}
44+
}
45+
}

services/taskConsumer/actions/vk/vk-authorizer.ts

Lines changed: 13 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { inject, injectable } from 'inversify';
22
import { Page } from 'puppeteer';
3-
import bluebird, { AggregateError } from 'bluebird';
3+
import { AggregateError } from 'bluebird';
44
import { LoggerInterface } from '../../../../lib/logger.interface';
55
import { ProxyInterface } from '../../proxy.interface';
66
import { AccountException } from '../../rpc-handlers/account.exception';
7-
import { CaptchaService } from '../../../../lib/captcha.service';
7+
import { ActionApplier } from './action-applier';
88

99
@injectable()
1010
export class VkAuthorizer {
1111
constructor(
1212
@inject('Logger') private readonly logger: LoggerInterface,
13-
@inject(CaptchaService) private readonly captcha: CaptchaService,
13+
@inject(ActionApplier) private readonly actionApplier: ActionApplier,
1414
) {}
1515

1616
async signInWithCookie(page: Page, login: string, remixsid: string): Promise<boolean> {
@@ -66,53 +66,19 @@ export class VkAuthorizer {
6666
password,
6767
);
6868

69-
const loginNavigationPromise = page.waitForNavigation({ timeout: 10000 });
70-
const waitForCaptchaPromise = page.waitFor(
71-
() => !!document.querySelector('.recaptcha iframe'),
72-
{ timeout: 10000 },
73-
);
74-
await page.click('#login_button');
75-
await bluebird
76-
.any([loginNavigationPromise as Promise<any>, waitForCaptchaPromise as Promise<any>])
77-
.catch(async error => {
78-
if (error instanceof AggregateError) {
79-
return page.reload({ waitUntil: 'networkidle2' });
80-
}
81-
82-
throw error;
69+
try {
70+
await this.actionApplier.click({
71+
page,
72+
goalAction: () => page.waitForNavigation({ timeout: 10000 }),
73+
selector: '#login_button',
74+
login,
8375
});
84-
85-
const hasCaptcha = await page.evaluate(() => !!document.querySelector('.recaptcha iframe'));
86-
if (hasCaptcha) {
87-
try {
88-
const captchaUrl = await page.evaluate(() =>
89-
document.querySelector('.recaptcha iframe').getAttribute('src'),
90-
);
91-
const urlObject = new URL(captchaUrl);
92-
const siteKey = urlObject.searchParams.get('k');
93-
const result = await this.captcha.solveRecaptchaV2({
94-
pageUrl: 'https://vk.com/login',
95-
siteKey,
96-
});
97-
const captchaNavigationPromise = page.waitForNavigation();
98-
await page.evaluate(
99-
token => {
100-
document.querySelector<HTMLInputElement>(
101-
'.recaptcha .g-recaptcha-response',
102-
).value = token;
103-
document.querySelector<HTMLInputElement>('#quick_recaptcha').value = token;
104-
document.querySelector<HTMLFormElement>('#quick_login_form').submit();
105-
},
106-
result,
107-
siteKey,
108-
);
109-
await captchaNavigationPromise;
110-
} catch (error) {
111-
error.code = 'captcha_failed';
112-
error.login = login;
113-
error.canRetry = true;
76+
} catch (error) {
77+
if (!(error instanceof AggregateError)) {
11478
throw error;
11579
}
80+
81+
await page.reload({ waitUntil: 'networkidle2' });
11682
}
11783

11884
await this.checkAccount(page, login);

services/taskConsumer/rpc-handlers/join-group-rpc.handler.ts

Lines changed: 28 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { VkUserCredentialsInterface } from '../../api/vk-users/vk-user-credentia
77
import { createBrowserPage } from '../actions/create-page';
88
import { hrefByGroupId } from '../../../lib/helper';
99
import { JoinGroupFailedException } from './join-group-failed.exception';
10-
import { AccountException } from './account.exception';
10+
import { ActionApplier } from '../actions/vk/action-applier';
1111

1212
type TaskArgsType = {
1313
userCredentials: VkUserCredentialsInterface;
@@ -20,6 +20,8 @@ export class JoinGroupRpcHandler extends AbstractRpcHandler {
2020

2121
@inject(VkAuthorizer) private readonly vkAuthorizer: VkAuthorizer;
2222

23+
@inject(ActionApplier) private readonly actionApplier: ActionApplier;
24+
2325
protected readonly method = 'joinGroup';
2426

2527
static readonly method = 'joinGroup';
@@ -63,22 +65,31 @@ export class JoinGroupRpcHandler extends AbstractRpcHandler {
6365
});
6466
}
6567

66-
const subscribeClicked = await page.evaluate(() => {
67-
const subscribeButton = document.querySelector<HTMLButtonElement>(
68-
'#public_subscribe',
69-
);
70-
const joinButton = document.querySelector<HTMLButtonElement>('#join_button');
71-
if (subscribeButton) {
72-
subscribeButton.click();
73-
return true;
74-
}
75-
76-
if (joinButton) {
77-
joinButton.click();
78-
return true;
79-
}
80-
81-
return false;
68+
const subscribeClicked = await this.actionApplier.callback({
69+
callback: () => {
70+
return page.evaluate(() => {
71+
const subscribeButton = document.querySelector<HTMLButtonElement>(
72+
'#public_subscribe',
73+
);
74+
const joinButton = document.querySelector<HTMLButtonElement>(
75+
'#join_button',
76+
);
77+
if (subscribeButton) {
78+
subscribeButton.click();
79+
return true;
80+
}
81+
82+
if (joinButton) {
83+
joinButton.click();
84+
return true;
85+
}
86+
87+
return false;
88+
});
89+
},
90+
login: userCredentials.login,
91+
goalAction: () => page.waitForSelector('#page_actions_btn'),
92+
page,
8293
});
8394

8495
if (!subscribeClicked) {
@@ -90,31 +101,6 @@ export class JoinGroupRpcHandler extends AbstractRpcHandler {
90101
);
91102
}
92103

93-
await page.waitForFunction(() => {
94-
const form = document.querySelector('#validation_phone_row');
95-
if (form) {
96-
return true;
97-
}
98-
99-
return !!document.querySelector('#page_actions_btn');
100-
});
101-
102-
const needPhoneConfirmation = await page.evaluate(() => {
103-
const form = document.querySelector('#validation_phone_row');
104-
return !!form;
105-
});
106-
107-
if (needPhoneConfirmation) {
108-
throw new AccountException(
109-
'Account requires phone confirmation',
110-
'phone_required',
111-
userCredentials.login,
112-
false,
113-
);
114-
}
115-
116-
await page.waitForSelector('#page_actions_btn');
117-
118104
this.logger.info({
119105
message: 'Подписался в группу',
120106
credentials: userCredentials,

0 commit comments

Comments
 (0)