2023-09-10 23:26:06 +09:00
|
|
|
import { AxiosInstance } from 'axios';
|
2024-02-27 22:52:08 +09:00
|
|
|
import { randomStr, sleep } from './index';
|
2023-09-10 23:26:06 +09:00
|
|
|
import { CreateAxiosProxy } from './proxyAgent';
|
2024-02-27 22:52:08 +09:00
|
|
|
import { Page } from 'puppeteer';
|
|
|
|
import CDP from 'chrome-remote-interface';
|
|
|
|
import { Config } from './config';
|
|
|
|
import fs from 'fs';
|
|
|
|
import puppeteer from 'puppeteer-extra';
|
|
|
|
import { closeOtherPages } from './puppeteer';
|
2023-09-10 23:26:06 +09:00
|
|
|
|
|
|
|
class CaptchaSolver {
|
|
|
|
private readonly apiKey: string;
|
|
|
|
private client: AxiosInstance;
|
|
|
|
|
|
|
|
constructor(apiKey: string) {
|
|
|
|
this.apiKey = apiKey;
|
|
|
|
this.client = CreateAxiosProxy({ baseURL: 'http://2captcha.com' }, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
async sendCaptcha(base64Image: string): Promise<string> {
|
|
|
|
const response = await this.client.post(`/in.php`, {
|
|
|
|
method: 'base64',
|
|
|
|
key: this.apiKey,
|
|
|
|
body: base64Image,
|
|
|
|
json: 1,
|
|
|
|
phrase: 1,
|
|
|
|
min_len: 6,
|
|
|
|
max_len: 6,
|
|
|
|
regsense: 1,
|
|
|
|
textinstructions:
|
|
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
|
|
|
|
});
|
|
|
|
|
|
|
|
const data = response.data;
|
|
|
|
|
|
|
|
if (data.status !== 1) {
|
|
|
|
throw new Error(`Failed to send captcha: ${data.request}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return data.request; // This will return the captcha ID
|
|
|
|
}
|
|
|
|
|
|
|
|
async getCaptchaResult(captchaId: string): Promise<string> {
|
|
|
|
let attempts = 0;
|
|
|
|
|
|
|
|
while (attempts < 10) {
|
|
|
|
// for example, trying 10 times before giving up
|
|
|
|
const response = await this.client.get(`/res.php`, {
|
|
|
|
params: {
|
|
|
|
key: this.apiKey,
|
|
|
|
action: 'get',
|
|
|
|
id: captchaId,
|
|
|
|
json: 1,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const data = response.data;
|
|
|
|
|
|
|
|
if (data.status === 1) {
|
|
|
|
return data.request;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data.request !== 'CAPCHA_NOT_READY') {
|
|
|
|
throw new Error(`Failed to retrieve captcha result: ${data.request}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
await sleep(10000);
|
|
|
|
attempts++;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error('Max attempts reached.');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getCaptchaCode(base64: string) {
|
|
|
|
if (!process.env.CAPTCHA2_APIKEY) {
|
|
|
|
throw new Error('not config CAPTCHA2_APIKEY in env');
|
|
|
|
}
|
|
|
|
const solver = new CaptchaSolver(process.env.CAPTCHA2_APIKEY);
|
|
|
|
try {
|
|
|
|
const captchaId = await solver.sendCaptcha(base64);
|
|
|
|
const captchaResult = await solver.getCaptchaResult(captchaId);
|
|
|
|
return captchaResult.replace(/[^a-zA-Z0-9]/g, '');
|
|
|
|
} catch (error: any) {
|
|
|
|
console.error('Error:', error.message);
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
}
|
2024-02-27 22:52:08 +09:00
|
|
|
|
|
|
|
export async function ifCF(page: Page) {
|
|
|
|
try {
|
|
|
|
await page.waitForSelector('#challenge-running', { timeout: 2 * 1000 });
|
|
|
|
return true;
|
|
|
|
} catch (e) {
|
|
|
|
console.log('no cf');
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function handleCF(
|
|
|
|
page: Page,
|
|
|
|
debug: boolean = false,
|
|
|
|
): Promise<Page> {
|
|
|
|
if (!(await ifCF(page))) {
|
|
|
|
return page;
|
|
|
|
}
|
|
|
|
const browser = page.browser();
|
|
|
|
const url = page.url();
|
|
|
|
const pageIdx = (await browser.pages()).findIndex((v) => v === page);
|
|
|
|
const wsEndpoint = browser.wsEndpoint();
|
|
|
|
browser.disconnect();
|
|
|
|
console.log('handle cf start');
|
|
|
|
const client: CDP.Client = await CDP({
|
|
|
|
target: wsEndpoint,
|
|
|
|
});
|
|
|
|
try {
|
|
|
|
const targets = await client.Target.getTargets();
|
|
|
|
await sleep(10000);
|
|
|
|
const target = targets.targetInfos.find((v) => v.url.indexOf(url) > -1);
|
|
|
|
if (!target) {
|
|
|
|
throw new Error('not found target');
|
|
|
|
}
|
|
|
|
const { sessionId } = await client.Target.attachToTarget({
|
|
|
|
targetId: target.targetId,
|
|
|
|
flatten: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
// 设置页面尺寸
|
|
|
|
await client.Page.enable(sessionId);
|
|
|
|
await client.Page.setDeviceMetricsOverride(
|
|
|
|
{
|
|
|
|
width: 1280,
|
|
|
|
height: 720,
|
|
|
|
deviceScaleFactor: 1,
|
|
|
|
mobile: false,
|
|
|
|
},
|
|
|
|
sessionId,
|
|
|
|
);
|
|
|
|
|
|
|
|
await client.Runtime.enable(sessionId);
|
|
|
|
await client.DOM.enable(sessionId);
|
|
|
|
const iframeExpression = `document.querySelector('iframe')`;
|
|
|
|
const { result: iframeResult } = await client.Runtime.evaluate(
|
|
|
|
{
|
|
|
|
expression: iframeExpression,
|
|
|
|
},
|
|
|
|
sessionId,
|
|
|
|
);
|
|
|
|
const { objectId: iframeObjectId } = iframeResult;
|
|
|
|
const { model: iframeModel } = await client.DOM.getBoxModel(
|
|
|
|
{
|
|
|
|
objectId: iframeObjectId,
|
|
|
|
},
|
|
|
|
sessionId,
|
|
|
|
);
|
|
|
|
const iframeCoordinates = iframeModel.border; // iframe 的坐标
|
|
|
|
let [x, y] = iframeCoordinates;
|
|
|
|
x = x + 9 + 14 + 8;
|
|
|
|
y = y + 10 + 14 + 8;
|
|
|
|
if (debug) {
|
|
|
|
await client.Runtime.enable(sessionId);
|
|
|
|
await client.Runtime.evaluate(
|
|
|
|
{
|
|
|
|
expression: `const dot = document.createElement('div');
|
|
|
|
dot.style.width = '100px';
|
|
|
|
dot.style.height = '100px';
|
|
|
|
dot.style.background = 'red';
|
|
|
|
dot.style.position = 'fixed';
|
|
|
|
dot.style.left = ${x} + 'px';
|
|
|
|
dot.style.top = ${y} + 'px';
|
|
|
|
document.body.appendChild(dot);`,
|
|
|
|
},
|
|
|
|
sessionId,
|
|
|
|
);
|
|
|
|
await client.Page.enable(sessionId);
|
|
|
|
const res = await client.Page.captureScreenshot(
|
|
|
|
{ format: 'png' },
|
|
|
|
sessionId,
|
|
|
|
);
|
|
|
|
fs.writeFileSync(`./run/png/${randomStr(6)}.png`, res.data, 'base64');
|
|
|
|
}
|
|
|
|
await client.Input.dispatchMouseEvent(
|
|
|
|
{
|
|
|
|
type: 'mousePressed',
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
button: 'left',
|
|
|
|
clickCount: 1,
|
|
|
|
},
|
|
|
|
sessionId,
|
|
|
|
);
|
|
|
|
await client.Input.dispatchMouseEvent(
|
|
|
|
{
|
|
|
|
type: 'mouseReleased',
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
button: 'left',
|
|
|
|
clickCount: 1,
|
|
|
|
},
|
|
|
|
sessionId,
|
|
|
|
);
|
|
|
|
await sleep(20 * 1000);
|
|
|
|
} catch (e) {
|
|
|
|
await client.Browser.close();
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log('handle cf end');
|
|
|
|
const newB = await puppeteer.connect({ browserWSEndpoint: wsEndpoint });
|
|
|
|
return (await newB.pages()).find(
|
|
|
|
(v) => v.url().indexOf('blank') === -1,
|
|
|
|
) as Page;
|
|
|
|
}
|