vk-captcha-bypass
Version:
Обход VK Captcha
160 lines (127 loc) • 4.88 kB
text/typescript
import {
IApiError,
IBaseCaptchaResponse,
IBypasserOptions,
ICaptchaResponse,
ICaptchaSettings,
IMousePoint,
IMouseTraceParams,
IPowHashParams,
IRedirectUriQueryParams,
} from './types';
import { generateMouseTrace, generatePoW } from './utils';
export class VKCaptchaBypasserError extends Error {
constructor(message: string) {
super(message);
}
}
export class APIError extends Error {
constructor(private error: IApiError) {
super(`${error.error_msg} (Code: ${error.error_code})`);
}
public get code() {
return this.error.error_code;
}
public get params() {
return this.error.request_params;
}
}
export class VKCaptchaBypasser {
private version: string;
private baseApiUrl: string;
private maxSensorsDataSizeKb: number;
constructor(options?: IBypasserOptions) {
this.version = options?.version || '5.199';
this.baseApiUrl = options?.baseApiUrl || 'https://api.vk.ru/';
this.maxSensorsDataSizeKb = options?.maxSensorsDataSizeKb || 900;
}
public async bypass(
redirectUri: string,
mouseTraceParams?: IMouseTraceParams,
): Promise<ICaptchaResponse> {
const pow = await this.loadPoWChallengeParams(redirectUri);
const hash = await generatePoW(pow.input, pow.difficulty);
const queryParams = new URLSearchParams(redirectUri.replace(/(.*)\?/, ''));
const { session_token, domain } = Object.fromEntries(
queryParams.entries(),
) as unknown as IRedirectUriQueryParams;
const initialParams = { session_token, domain };
const { bridge_sensors_list: sensorsList } = await this.request<ICaptchaSettings>(
'captchaNotRobot.settings',
initialParams,
);
await this.request<IBaseCaptchaResponse>('captchaNotRobot.componentDone', initialParams);
const sensorsData = this.buildSensorsData(sensorsList, mouseTraceParams);
const captcha = await this.request<ICaptchaResponse>('captchaNotRobot.check', {
...initialParams,
...sensorsData,
hash,
answer: 'e30=',
});
await this.request<IBaseCaptchaResponse>('captchaNotRobot.endSession', initialParams);
return captcha;
}
private async request<T>(method: string, params: Record<string, unknown>) {
const entries = Object.entries({ ...params, v: this.version });
const encoded = entries.map(([key, value]) => {
if (typeof value === 'object' && value !== null) return [key, JSON.stringify(value)];
return [key, String(value)];
});
const body = new URLSearchParams(Object.fromEntries(encoded));
const contentLength = Buffer.from(body.toString()).byteLength;
const url = new URL(`/method/${method}`, this.baseApiUrl);
const fetchResponse = await fetch(url.toString(), {
headers: {
'content-type': 'application/x-www-form-urlencoded',
'content-length': contentLength.toString(),
},
body,
method: 'POST',
});
const response = (await fetchResponse.json()) as any;
if ('error' in response) throw new APIError(response.error);
if ('status' in response && response.status === 'ERROR')
throw new APIError({
error_code: 0,
error_msg: `APIError: ${JSON.stringify(response)}`,
request_params: [
...body
.entries()
.map(([key, value]) => ({ key, value }))
.toArray(),
],
});
return response.response as T;
}
private async loadPoWChallengeParams(redirectUri: string): Promise<IPowHashParams> {
const fetchResponse = await fetch(redirectUri);
const html = await fetchResponse.text();
const [, input] = html.match(/const powInput\s*=\s*"([^']+)";/i) || [];
if (typeof input !== 'string') {
throw new VKCaptchaBypasserError(`No "powInput" value`);
}
const [, rawDifficulty] = html.match(/const difficulty\s*=\s*(\d+);/) || [];
if (typeof rawDifficulty !== 'string') {
throw new VKCaptchaBypasserError(`No "difficulty" value`);
}
const difficulty = Number(rawDifficulty);
return { difficulty, input };
}
private buildSensorsData(
sensorsList: ICaptchaSettings['bridge_sensors_list'],
mouseTraceParams?: IMouseTraceParams,
): Record<string, IMousePoint[]> {
let cursor = generateMouseTrace(mouseTraceParams);
const maxBytes = this.maxSensorsDataSizeKb * 1024;
const avgBytesPerPoint = 20;
let maxPoints = Math.floor(maxBytes / avgBytesPerPoint);
if (cursor.length > maxPoints) {
cursor = cursor.slice(0, maxPoints);
}
let sensors: Record<string, IMousePoint[]> = {};
for (const sensor of sensorsList) {
sensors[sensor] = sensor === 'cursor' ? cursor : [];
}
return sensors;
}
}