diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..0e75fe5 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +node_modules +dist +coverage diff --git a/README.md b/README.md index 7781f60..77ff532 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,47 @@ Via [NPM](https://www.npmjs.com/package/@zennolab_com/capmonstercloud-client): ## Usage with Node (with or without Typescript) ```javascript -const { CapMonsterCloudClientFactory, ClientOptions, RecaptchaV2Request } = require('@zennolab_com/capmonstercloud-client'); +const { CapMonsterCloudClientFactory, ClientOptions } = require('@zennolab_com/capmonstercloud-client'); async function run() { const cmcClient = CapMonsterCloudClientFactory.Create(new ClientOptions({ clientKey: '' })); console.log(await cmcClient.getBalance()); + console.log(await cmcClient.solve({ + task: { + type: 'NoCaptchaTaskProxyless', + websiteURL: 'https://lessons.zennolab.com/captchas/recaptcha/v2_simple.php?level=high', + websiteKey: '6Lcg7CMUAAAAANphynKgn9YAgA4tQ2KI_iqRyTwd', + }, + })); +} + +run().catch(console.error); +``` + +You can also use the lower-level API if you want to manage polling yourself: + +```javascript +const createdTask = await cmcClient.createTask({ + task: { + type: 'NoCaptchaTaskProxyless', + websiteURL: 'https://lessons.zennolab.com/captchas/recaptcha/v2_simple.php?level=high', + websiteKey: '6Lcg7CMUAAAAANphynKgn9YAgA4tQ2KI_iqRyTwd', + }, +}); + +const result = await cmcClient.getTaskResult(createdTask.taskId); +``` + +The plain payload API validates required fields before sending a request. Invalid input throws `ValidationError`. + +The legacy class-based API is still supported: + +```javascript +const { CapMonsterCloudClientFactory, ClientOptions, RecaptchaV2Request } = require('@zennolab_com/capmonstercloud-client'); + +async function runLegacy() { + const cmcClient = CapMonsterCloudClientFactory.Create(new ClientOptions({ clientKey: '' })); const recaptchaV2Request = new RecaptchaV2Request({ websiteURL: 'https://lessons.zennolab.com/captchas/recaptcha/v2_simple.php?level=high', websiteKey: '6Lcg7CMUAAAAANphynKgn9YAgA4tQ2KI_iqRyTwd', @@ -25,15 +60,7 @@ async function run() { console.log(await cmcClient.Solve(recaptchaV2Request)); } -run() - .then(() => { - console.log('DONE'); - process.exit(0); - }) - .catch((err) => { - console.error(err); - process.exit(1); - }); +runLegacy().catch(console.error); ``` ## Usage with Browser (with or without Typescript) @@ -42,26 +69,25 @@ Browser implementation use [fetch](https://caniuse.com/fetch) instead of [http(s For browser usage you need some Module Bundler (e.g. [Webpack](https://webpack.js.org/)). ```javascript -import { CapMonsterCloudClientFactory, ClientOptions, RecaptchaV2Request } from '@zennolab_com/capmonstercloud-client'; +import { CapMonsterCloudClientFactory, ClientOptions } from '@zennolab_com/capmonstercloud-client'; document.addEventListener('DOMContentLoaded', async () => { const cmcClient = CapMonsterCloudClientFactory.Create(new ClientOptions({ clientKey: '' })); console.log(await cmcClient.getBalance()); - const recaptchaV2Request = new RecaptchaV2Request({ - websiteURL: 'https://lessons.zennolab.com/captchas/recaptcha/v2_simple.php?level=high', - websiteKey: '6Lcg7CMUAAAAANphynKgn9YAgA4tQ2KI_iqRyTwd', - proxy: { + console.log(await cmcClient.solve({ + task: { + type: 'NoCaptchaTask', + websiteURL: 'https://lessons.zennolab.com/captchas/recaptcha/v2_simple.php?level=high', + websiteKey: '6Lcg7CMUAAAAANphynKgn9YAgA4tQ2KI_iqRyTwd', proxyType: 'http', proxyAddress: '8.8.8.8', proxyPort: 8080, proxyLogin: 'proxyLoginHere', proxyPassword: 'proxyPasswordHere', + userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.132 Safari/537.36', }, - userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.132 Safari/537.36', - }); - - console.log(await cmcClient.Solve(recaptchaV2Request)); + })); }); ``` diff --git a/package.json b/package.json index b6e4e08..7584a52 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@zennolab_com/capmonstercloud-client", - "version": "2.7.0", + "version": "3.0.0", "description": "Official JS client library for https://capmonster.cloud/ captcha recognition service", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -24,7 +24,9 @@ "build": "node js-rimraf.js && npx tsc", "test": "jest", "test-unit": "jest -c jest.config.unit.js", - "test-integr": "jest -c jest.config.integration.js" + "test-integr": "jest -c jest.config.integration.js", + "lint": "eslint . --ext .ts,.js", + "lint:fix": "eslint . --ext .ts,.js --fix" }, "author": "", "license": "ISC", diff --git a/src/CapMonsterCloudClient.new-api.i.spec.ts b/src/CapMonsterCloudClient.new-api.i.spec.ts new file mode 100644 index 0000000..97731c8 --- /dev/null +++ b/src/CapMonsterCloudClient.new-api.i.spec.ts @@ -0,0 +1,193 @@ +import { createServerMock } from '../tests/helpers'; +import { CapMonsterCloudClientFactory } from './CapMonsterCloudClientFactory'; +import { ClientOptions } from './ClientOptions'; +import { TaskResultType } from './GetTaskResult'; +import { CreateTaskPayload, ValidationError } from './TaskPayload'; + +describe('Check integration tests for plain CapMonsterCloudClient API', () => { + it('should call createTask with plain task payload', async () => { + const srv = await createServerMock({ + responses: [{ responseBody: '{"errorId":0,"taskId":123456}' }], + }); + + const cmcClient = CapMonsterCloudClientFactory.Create( + new ClientOptions({ clientKey: '', serviceUrl: `http://127.0.0.1:${srv.address.port}`, softId: 55 }), + ); + + const result = await cmcClient.createTask({ + task: { + type: 'NoCaptchaTaskProxyless', + websiteURL: 'https://lessons.zennolab.com/captchas/recaptcha/v2_simple.php?level=high', + websiteKey: '6Lcg7CMUAAAAANphynKgn9YAgA4tQ2KI_iqRyTwd', + }, + }); + + expect(result).toMatchObject({ errorId: 0, taskId: 123456 }); + expect(JSON.parse(srv.caughtRequests[0].body as string)).toMatchObject({ + clientKey: '', + softId: 55, + task: { + type: 'NoCaptchaTaskProxyless', + websiteURL: 'https://lessons.zennolab.com/captchas/recaptcha/v2_simple.php?level=high', + websiteKey: '6Lcg7CMUAAAAANphynKgn9YAgA4tQ2KI_iqRyTwd', + }, + }); + + await srv.destroy(); + }); + + it('should use payload softId over client options softId', async () => { + const srv = await createServerMock({ + responses: [{ responseBody: '{"errorId":0,"taskId":123456}' }], + }); + + const cmcClient = CapMonsterCloudClientFactory.Create( + new ClientOptions({ clientKey: '', serviceUrl: `http://127.0.0.1:${srv.address.port}`, softId: 55 }), + ); + + await cmcClient.createTask({ + softId: 56, + task: { + type: 'CustomTask', + class: 'CustomCaptcha', + }, + }); + + expect(JSON.parse(srv.caughtRequests[0].body as string)).toMatchObject({ + softId: 56, + task: { + type: 'CustomTask', + class: 'CustomCaptcha', + }, + }); + + await srv.destroy(); + }); + + it('should call createTask with Cloudflare Waiting Room payload', async () => { + const srv = await createServerMock({ + responses: [{ responseBody: '{"errorId":0,"taskId":123456}' }], + }); + + const cmcClient = CapMonsterCloudClientFactory.Create( + new ClientOptions({ clientKey: '', serviceUrl: `http://127.0.0.1:${srv.address.port}` }), + ); + + const payload: CreateTaskPayload = { + task: { + type: 'TurnstileTask', + websiteURL: 'https://example.com', + websiteKey: 'xxxxxxxxxx', + cloudflareTaskType: 'wait_room', + htmlPageBase64: 'PCFET0NUWVBFIGh0...vYm9keT48L2h0bWw+', + userAgent: 'userAgentPlaceholder', + proxyType: 'http', + proxyAddress: '8.8.8.8', + proxyPort: 8080, + proxyLogin: 'proxyLoginHere', + proxyPassword: 'proxyPasswordHere', + }, + }; + + const result = await cmcClient.createTask(payload); + + expect(result).toMatchObject({ errorId: 0, taskId: 123456 }); + expect(JSON.parse(srv.caughtRequests[0].body as string)).toMatchObject({ + task: { + type: 'TurnstileTask', + cloudflareTaskType: 'wait_room', + htmlPageBase64: 'PCFET0NUWVBFIGh0...vYm9keT48L2h0bWw+', + proxyType: 'http', + }, + }); + + await srv.destroy(); + }); + + it('should request getTaskResult by taskId', async () => { + const srv = await createServerMock({ + responses: [{ responseBody: '{"errorId":0,"status":"ready","solution":{"gRecaptchaResponse":"token"}}' }], + }); + + const cmcClient = CapMonsterCloudClientFactory.Create( + new ClientOptions({ clientKey: '', serviceUrl: `http://127.0.0.1:${srv.address.port}` }), + ); + + const result = await cmcClient.getTaskResult(123456); + + expect(result).toMatchObject({ + type: TaskResultType.Completed, + solution: { + gRecaptchaResponse: 'token', + }, + }); + expect(JSON.parse(srv.caughtRequests[0].body as string)).toMatchObject({ + clientKey: '', + taskId: 123456, + }); + + await srv.destroy(); + }); + + it('should request getTaskResult with Cloudflare clearance solution', async () => { + const srv = await createServerMock({ + responses: [{ responseBody: '{"errorId":0,"status":"ready","solution":{"cf_clearance":"cookie-value"}}' }], + }); + + const cmcClient = CapMonsterCloudClientFactory.Create( + new ClientOptions({ clientKey: '', serviceUrl: `http://127.0.0.1:${srv.address.port}` }), + ); + + const result = await cmcClient.getTaskResult(123456); + + expect(result).toMatchObject({ + type: TaskResultType.Completed, + solution: { + cf_clearance: 'cookie-value', + }, + }); + + await srv.destroy(); + }); + + it('should solve plain task payload', async () => { + const srv = await createServerMock({ + responses: [ + { responseBody: '{"errorId":0,"taskId":123456}' }, + { responseBody: '{"errorId":0,"status":"ready","solution":{"gRecaptchaResponse":"token"}}' }, + ], + }); + + const cmcClient = CapMonsterCloudClientFactory.Create( + new ClientOptions({ clientKey: '', serviceUrl: `http://127.0.0.1:${srv.address.port}` }), + ); + + const result = await cmcClient.solve( + { + task: { + type: 'NoCaptchaTaskProxyless', + websiteURL: 'https://lessons.zennolab.com/captchas/recaptcha/v2_simple.php?level=high', + websiteKey: '6Lcg7CMUAAAAANphynKgn9YAgA4tQ2KI_iqRyTwd', + }, + }, + { + firstRequestDelay: 0, + requestsInterval: 0, + timeout: 100, + }, + ); + + expect(result.solution).toMatchObject({ + gRecaptchaResponse: 'token', + }); + expect(srv.caughtRequests).toHaveLength(2); + + await srv.destroy(); + }); + + it('should validate plain createTask payload before request', async () => { + const cmcClient = CapMonsterCloudClientFactory.Create(new ClientOptions({ clientKey: '' })); + + await expect(cmcClient.createTask({ task: {} as never })).rejects.toBeInstanceOf(ValidationError); + }); +}); diff --git a/src/CapMonsterCloudClient.ts b/src/CapMonsterCloudClient.ts index 4bcc237..18e0719 100644 --- a/src/CapMonsterCloudClient.ts +++ b/src/CapMonsterCloudClient.ts @@ -1,14 +1,15 @@ import { CaptchaResult } from './CaptchaResult'; +import { CapmonsterCloudClientError } from './ClientError'; import { ClientOptions } from './ClientOptions'; -import { CreateTaskResponse, CreateTaskResponseError } from './CreateTask'; +import { CreateTaskResponse } from './CreateTask'; import { ErrorCodeConverter } from './ErrorCodeConverter'; -import { ErrorType } from './ErrorType'; import { GetBalanceError, GetBalanceResponse, GetBalanceResponseError, GetBalanceResponseSuccess } from './GetBalance'; -import { GetTaskResultResponse, TaskCompletedSolution, TaskResult, TaskResultStatus, TaskResultType } from './GetTaskResult'; +import { TaskCompletedSolution, TaskResult } from './GetTaskResult'; import { detectResultTimeouts, GetResultTimeouts } from './GetResultTimeouts'; -import { HttpClient, HttpStatusCode, HttpStatusError, JSONParseError } from './HttpClient'; +import { HttpClient, HttpStatusError, JSONParseError } from './HttpClient'; import { Task } from './Requests/Task'; -import { debugTask } from './Logger'; +import { CreateTaskPayload, GetTaskResultPayload, TaskPayload, validateCreateTaskPayload, validateTaskId } from './TaskPayload'; +import { createTaskRequest, detectResultTimeoutsOrDefault, getTaskResultRequest, solveTask, TaskOperationContext } from './TaskOperations'; import { FunCaptchaResponse } from './Responses/FunCaptchaResponse'; import { FunCaptchaRequest } from './Requests/FunCaptchaRequest'; @@ -26,7 +27,7 @@ import { RecaptchaV2Request } from './Requests/RecaptchaV2Request'; import { RecaptchaV3ProxylessRequest } from './Requests/RecaptchaV3ProxylessRequest'; import { RecaptchaV3Response } from './Responses/RecaptchaV3Response'; import { TurnstileResponse } from './Responses/TurnstileResponse'; -import { SerializeObject } from './Requests/RequestsSerialization'; +import { serializeGetBalancePayload } from './Requests/RequestsSerialization'; import { ComplexImageRecaptchaRequest } from './Requests/ComplexImageRecaptchaRequest'; import { ComplexImageResponse } from './Responses/ComplexImageResponse'; import { ComplexImageHCaptchaRequest } from './Requests/ComplexImageHCaptchaRequest'; @@ -68,10 +69,7 @@ import { AlibabaResponse } from './Responses/AlibabaResponse'; import { FriendlyRequest } from './Requests/FriendlyRequest'; import { FriendlyResponse } from './Responses/FriendlyResponse'; -/** - * Base type for capmonster.cloud Client exceptions - */ -export class CapmonsterCloudClientError extends Error {} +export { CapmonsterCloudClientError } from './ClientError'; /** * CapMonsterCloud client @@ -79,6 +77,10 @@ export class CapmonsterCloudClientError extends Error {} export class CapMonsterCloudClient { constructor(private _options: ClientOptions, private _httpClient: HttpClient) {} + private get taskOperationContext(): TaskOperationContext { + return { options: this._options, httpClient: this._httpClient }; + } + /** * Gets current amount of money on balance * {@link https://zennolab.atlassian.net/wiki/spaces/APIS/pages/655432/getBalance+retrieve+account+balance} @@ -87,7 +89,7 @@ export class CapMonsterCloudClient { try { const response = await this._httpClient.post( 'getBalance', - JSON.stringify(SerializeObject({ clientKey: this._options.clientKey })), + JSON.stringify(serializeGetBalancePayload(this._options.clientKey)), cancellationController, ); @@ -109,26 +111,20 @@ export class CapMonsterCloudClient { * captcha task creating * {@link https://zennolab.atlassian.net/wiki/spaces/APIS/pages/393308/createTask+captcha+task+creating} */ - private async CreateTask(task: Task, cancellationController?: AbortController): Promise { - try { - const response = await this._httpClient.post( - 'createTask', - JSON.stringify( - SerializeObject({ clientKey: this._options.clientKey, task, softId: this._options.softId || ClientOptions.defaultSoftId }), - ), - cancellationController, - ); - debugTask('create task response', response); + private async CreateTask( + task: Task | TaskPayload, + cancellationController?: AbortController, + softId?: number, + ): Promise { + return createTaskRequest(this.taskOperationContext, task, cancellationController, softId); + } - return response; - } catch (err) { - if (err instanceof HttpStatusError) { - throw new CapmonsterCloudClientError(`Cannot create task. Status code was ${err.statusCode}`); - } else if (err instanceof JSONParseError) { - throw new CapmonsterCloudClientError(`Cannot parse create task response. Response was: ${err.responseBody}`); - } - throw err; - } + /** + * Creates captcha task using payload compatible with the public createTask API. + */ + public async createTask(payload: CreateTaskPayload, cancellationController?: AbortController): Promise { + validateCreateTaskPayload(payload); + return this.CreateTask(payload.task, cancellationController, payload.softId); } /** @@ -139,38 +135,37 @@ export class CapMonsterCloudClient { taskId: number, cancellationController?: AbortController, ): Promise> { - try { - const response = await this._httpClient.post>( - 'getTaskResult', - JSON.stringify(SerializeObject({ clientKey: this._options.clientKey, taskId })), - cancellationController, - ); - debugTask('GetTaskResult() response', response); - - if (response.errorId !== 0) { - if (response.errorCode.includes('CAPTCHA_NOT_READY')) { - return { type: TaskResultType.InProgress }; - } else { - return { type: TaskResultType.Failed, error: ErrorCodeConverter.convert(response.errorCode) }; - } - } + return getTaskResultRequest(this.taskOperationContext, taskId, cancellationController); + } - if (response.status === TaskResultStatus.ready) { - return { type: TaskResultType.Completed, solution: response.solution as S }; - } + /** + * Requests task result by task id. + */ + public async getTaskResult( + taskIdOrPayload: number | GetTaskResultPayload, + cancellationController?: AbortController, + ): Promise> { + const taskId = typeof taskIdOrPayload === 'number' ? taskIdOrPayload : taskIdOrPayload.taskId; + validateTaskId(taskId); + return this.GetTaskResult(taskId, cancellationController); + } - return { type: TaskResultType.InProgress }; - } catch (err) { - if (err instanceof HttpStatusError) { - if (err.statusCode === HttpStatusCode.ServiceUnavailable) { - return { type: TaskResultType.InProgress }; - } - throw new CapmonsterCloudClientError(`Cannot get task result. Status code was ${err.statusCode}`); - } else if (err instanceof JSONParseError) { - throw new CapmonsterCloudClientError(`Cannot parse get task result response. Response was: ${err.responseBody}`); - } - throw err; - } + /** + * Creates task and polls for result using payload compatible with the public createTask API. + */ + public async solve( + payload: CreateTaskPayload, + resultTimeouts?: GetResultTimeouts, + cancellationController?: AbortController, + ): Promise> { + validateCreateTaskPayload(payload); + return solveTask( + this.taskOperationContext, + payload.task, + resultTimeouts || detectResultTimeoutsOrDefault(payload.task), + cancellationController, + payload.softId, + ); } /** * Solve FunCaptchaTask task @@ -371,15 +366,6 @@ export class CapMonsterCloudClient { resultTimeouts?: GetResultTimeouts, cancellationController?: AbortController, ): Promise>; - /** - * Solve Amazon task - * You will get response within 10 - 180 secs period depending on service workload. - */ - public async Solve( - task: AmazonRequest, - resultTimeouts?: GetResultTimeouts, - cancellationController?: AbortController, - ): Promise>; /** * Solve Turnstile task * You will get response within 10 - 180 secs period depending on service workload. @@ -454,57 +440,6 @@ export class CapMonsterCloudClient { resultTimeouts: GetResultTimeouts = detectResultTimeouts(task), cancellationController?: AbortController, ): Promise> { - debugTask('task in', task); - debugTask('resultTimeouts in', resultTimeouts); - const createdTask = await this.CreateTask(task, cancellationController); - if (createdTask.errorId !== 0) { - return new CaptchaResult({ - error: ErrorCodeConverter.convert((createdTask as CreateTaskResponseError).errorCode), - }); - } - - const firstRequestDelay = task.nocache ? resultTimeouts.firstRequestNoCacheDelay : resultTimeouts.firstRequestDelay; - debugTask('firstRequestDelay', firstRequestDelay); - await new Promise((resolve) => setTimeout(resolve, firstRequestDelay)); - - let signalAborted = (cancellationController && cancellationController.signal.aborted) || false; - const wholeTimeoutId = setTimeout(() => { - signalAborted = true; - cancellationController && cancellationController.abort(); - debugTask('cancellationController abort()'); - }, resultTimeouts.timeout); - - while (signalAborted === false) { - try { - const result = await this.GetTaskResult(createdTask.taskId, cancellationController); - switch (result.type) { - case TaskResultType.Failed: - clearTimeout(wholeTimeoutId); - return new CaptchaResult({ error: result.error }); - case TaskResultType.Completed: - clearTimeout(wholeTimeoutId); - return new CaptchaResult({ solution: result.solution as TaskCompletedSolution }); - case TaskResultType.InProgress: - default: - break; - } - } catch (err) { - if (signalAborted) { - break; - } - - clearTimeout(wholeTimeoutId); - throw err; - } - - if (signalAborted) { - break; - } - debugTask('requestsInterval', resultTimeouts.requestsInterval); - await new Promise((resolve) => setTimeout(resolve, resultTimeouts.requestsInterval)); - } - - clearTimeout(wholeTimeoutId); - return new CaptchaResult({ error: ErrorType.Timeout }); + return solveTask(this.taskOperationContext, task, resultTimeouts, cancellationController); } } diff --git a/src/ClientError.ts b/src/ClientError.ts new file mode 100644 index 0000000..89d5005 --- /dev/null +++ b/src/ClientError.ts @@ -0,0 +1,4 @@ +/** + * Base type for capmonster.cloud Client exceptions. + */ +export class CapmonsterCloudClientError extends Error {} diff --git a/src/GetTaskResult.ts b/src/GetTaskResult.ts index 883782e..8aca460 100644 --- a/src/GetTaskResult.ts +++ b/src/GetTaskResult.ts @@ -20,6 +20,12 @@ import { TSPDResponse } from './Responses/TSPDResponse'; import { HuntResponse } from './Responses/HuntResponse'; import { AlibabaResponse } from './Responses/AlibabaResponse'; import { FriendlyResponse } from './Responses/FriendlyResponse'; +import { TurnstileResponse } from './Responses/TurnstileResponse'; +import { ProsopoResponse } from './Responses/ProsopoResponse'; +import { TemuResponse } from './Responses/TemuResponse'; +import { YidunResponse } from './Responses/YidunResponse'; +import { MTCaptchaResponse } from './Responses/MTCaptchaResponse'; +import { RecaptchaV3EnterpriseResponse } from './Responses/RecaptchaV3EnterpriseResponse'; export enum TaskResultType { Failed = 'Failed', @@ -44,6 +50,7 @@ export type TaskCompletedSolution = | RecaptchaV2EnterpriseResponse | RecaptchaV2Response | RecaptchaV3Response + | RecaptchaV3EnterpriseResponse | ComplexImageResponse | DataDomeResponse | TenDIResponse @@ -57,7 +64,12 @@ export type TaskCompletedSolution = | TSPDResponse | HuntResponse | AlibabaResponse - | FriendlyResponse; + | FriendlyResponse + | TurnstileResponse + | ProsopoResponse + | TemuResponse + | YidunResponse + | MTCaptchaResponse; export type TaskCompleted = { type: TaskResultType.Completed; diff --git a/src/Requests/FunCaptchaRequest/FunCaptchaRequestBase.ts b/src/Requests/FunCaptchaRequest/FunCaptchaRequestBase.ts index db36077..2fca7d7 100644 --- a/src/Requests/FunCaptchaRequest/FunCaptchaRequestBase.ts +++ b/src/Requests/FunCaptchaRequest/FunCaptchaRequestBase.ts @@ -55,7 +55,16 @@ export abstract class FunCaptchaRequestBase extends CaptchaRequestBase { */ public userAgent?: string; - constructor({ type, nocache, websiteURL, websitePublicKey, funcaptchaApiJSSubdomain, data, cookies, userAgent }: FunCaptchaRequestBaseIn) { + constructor({ + type, + nocache, + websiteURL, + websitePublicKey, + funcaptchaApiJSSubdomain, + data, + cookies, + userAgent, + }: FunCaptchaRequestBaseIn) { super({ type, nocache }); this.websiteURL = websiteURL; this.websitePublicKey = websitePublicKey; diff --git a/src/Requests/RequestsSerialization.api.u.spec.ts b/src/Requests/RequestsSerialization.api.u.spec.ts new file mode 100644 index 0000000..75ed008 --- /dev/null +++ b/src/Requests/RequestsSerialization.api.u.spec.ts @@ -0,0 +1,52 @@ +import { + serializeCreateTaskPayload, + serializeGetBalancePayload, + serializeGetTaskResultPayload, + SerializeObject, +} from './RequestsSerialization'; + +describe('Check unit tests for explicit API payload serialization', () => { + it('should serialize getBalance payload', () => { + expect(serializeGetBalancePayload('')).toEqual({ + clientKey: '', + }); + }); + + it('should serialize createTask payload without undefined softId', () => { + expect( + serializeCreateTaskPayload({ + clientKey: '', + task: { + type: 'NoCaptchaTaskProxyless', + websiteURL: 'https://example.com', + websiteKey: 'site-key', + }, + }), + ).toEqual({ + clientKey: '', + task: { + type: 'NoCaptchaTaskProxyless', + websiteURL: 'https://example.com', + websiteKey: 'site-key', + }, + }); + }); + + it('should serialize getTaskResult payload', () => { + expect( + serializeGetTaskResultPayload({ + clientKey: '', + taskId: 123456, + }), + ).toEqual({ + clientKey: '', + taskId: 123456, + }); + }); + + it('should keep SerializeObject compatibility wrapper', () => { + expect(SerializeObject({ clientKey: '' })).toEqual({ + clientKey: '', + }); + }); +}); diff --git a/src/Requests/RequestsSerialization.ts b/src/Requests/RequestsSerialization.ts index 7ff463f..6805fd1 100644 --- a/src/Requests/RequestsSerialization.ts +++ b/src/Requests/RequestsSerialization.ts @@ -1,5 +1,59 @@ import { Task } from './Task'; +import { TaskPayload } from '../TaskPayload'; -export function SerializeObject(options: { task?: Task; softId?: number; taskId?: number; clientKey: string }) { - return { ...options }; +export type GetBalanceApiPayload = { + clientKey: string; +}; + +export type CreateTaskApiPayload = { + clientKey: string; + task: Task | TaskPayload; + softId?: number; +}; + +export type GetTaskResultApiPayload = { + clientKey: string; + taskId: number; +}; + +export type ApiPayload = GetBalanceApiPayload | CreateTaskApiPayload | GetTaskResultApiPayload; + +function withoutUndefined>(payload: T): T { + return Object.keys(payload).reduce((result, key) => { + if (payload[key] !== undefined) { + result[key as keyof T] = payload[key] as T[keyof T]; + } + return result; + }, {} as T); +} + +export function serializeGetBalancePayload(clientKey: string): GetBalanceApiPayload { + return { clientKey }; +} + +export function serializeCreateTaskPayload(payload: CreateTaskApiPayload): CreateTaskApiPayload { + return withoutUndefined({ + clientKey: payload.clientKey, + task: payload.task, + softId: payload.softId, + }); +} + +export function serializeGetTaskResultPayload(payload: GetTaskResultApiPayload): GetTaskResultApiPayload { + return { + clientKey: payload.clientKey, + taskId: payload.taskId, + }; +} + +export function SerializeObject(options: ApiPayload): ApiPayload { + if ('task' in options) { + return serializeCreateTaskPayload(options); + } + + if ('taskId' in options) { + return serializeGetTaskResultPayload(options); + } + + return serializeGetBalancePayload(options.clientKey); } diff --git a/src/Requests/Task.ts b/src/Requests/Task.ts index 02dd683..26d74a8 100644 --- a/src/Requests/Task.ts +++ b/src/Requests/Task.ts @@ -26,6 +26,7 @@ import { HuntRequest } from './HuntRequest'; import { AlibabaRequest } from './AlibabaRequest'; import { FriendlyRequest } from './FriendlyRequest'; import { TSPDRequest } from './TSPDRequest'; +import { RecaptchaV3EnterpriseRequest } from './RecaptchaV3EnterpriseRequest'; /** * Universal type for recognition request @@ -38,6 +39,7 @@ export type Task = | RecaptchaV2EnterpriseRequest | RecaptchaV2Request | RecaptchaV3ProxylessRequest + | RecaptchaV3EnterpriseRequest | ComplexImageHCaptchaRequest | ComplexImageRecaptchaRequest | ComplexImageFunCaptchaRequest diff --git a/src/Requests/TurnstileRequest/TurnstileRequest.ts b/src/Requests/TurnstileRequest/TurnstileRequest.ts index cd720f8..2d18689 100644 --- a/src/Requests/TurnstileRequest/TurnstileRequest.ts +++ b/src/Requests/TurnstileRequest/TurnstileRequest.ts @@ -20,7 +20,13 @@ export type TurnstileCfClearanceType = Pick> & { + cloudflareTaskType: 'wait_room'; + userAgent: string; + htmlPageBase64: string; +}; + +export type TurnstileRequestIn = (TurnstileTokenType | TurnstileCfClearanceType | TurnstileWaitRoomType) & { proxy?: ProxyInfoIn; }; @@ -41,11 +47,13 @@ export class TurnstileRequest extends TurnstileRequestBase { this.cloudflareTaskType = argsObj.cloudflareTaskType; this.userAgent = argsObj.userAgent; - if (argsObj.cloudflareTaskType === 'cf_clearance') { + if (argsObj.cloudflareTaskType === 'cf_clearance' || argsObj.cloudflareTaskType === 'wait_room') { this.htmlPageBase64 = argsObj?.htmlPageBase64; - this.data = argsObj?.data; - this.pageData = argsObj?.pageData; - this.apiJsUrl = argsObj?.apiJsUrl; + if (argsObj.cloudflareTaskType === 'cf_clearance') { + this.data = argsObj?.data; + this.pageData = argsObj?.pageData; + this.apiJsUrl = argsObj?.apiJsUrl; + } } if (argsObj.cloudflareTaskType === 'token') { this.data = argsObj?.data; diff --git a/src/Responses/TurnstileResponse.ts b/src/Responses/TurnstileResponse.ts index f9fcfce..7faede7 100644 --- a/src/Responses/TurnstileResponse.ts +++ b/src/Responses/TurnstileResponse.ts @@ -1,10 +1,19 @@ /** * Turnstile recognition response */ -export type TurnstileResponse = { +export type TurnstileTokenResponse = { /** * Captcha answer */ token: string; userAgent: string; }; + +export type TurnstileCfClearanceResponse = { + /** + * Cloudflare clearance cookie. + */ + cf_clearance: string; +}; + +export type TurnstileResponse = TurnstileTokenResponse | TurnstileCfClearanceResponse; diff --git a/src/TaskOperations.ts b/src/TaskOperations.ts new file mode 100644 index 0000000..028792a --- /dev/null +++ b/src/TaskOperations.ts @@ -0,0 +1,159 @@ +import { CaptchaResult } from './CaptchaResult'; +import { CapmonsterCloudClientError } from './ClientError'; +import { ClientOptions } from './ClientOptions'; +import { CreateTaskResponse, CreateTaskResponseError } from './CreateTask'; +import { ErrorCodeConverter } from './ErrorCodeConverter'; +import { ErrorType } from './ErrorType'; +import { GetTaskResultResponse, TaskCompletedSolution, TaskResult, TaskResultStatus, TaskResultType } from './GetTaskResult'; +import { detectResultTimeouts, GetResultTimeouts } from './GetResultTimeouts'; +import { HttpClient, HttpStatusCode, HttpStatusError, JSONParseError } from './HttpClient'; +import { debugTask } from './Logger'; +import { serializeCreateTaskPayload, serializeGetTaskResultPayload } from './Requests/RequestsSerialization'; +import { Task } from './Requests/Task'; +import { TaskPayload } from './TaskPayload'; +import { TaskType } from './TaskType'; + +export type TaskOperationContext = { + options: ClientOptions; + httpClient: HttpClient; +}; + +export async function createTaskRequest( + context: TaskOperationContext, + task: Task | TaskPayload, + cancellationController?: AbortController, + softId?: number, +): Promise { + try { + const resolvedSoftId = softId === undefined ? context.options.softId || ClientOptions.defaultSoftId : softId; + const response = await context.httpClient.post( + 'createTask', + JSON.stringify( + serializeCreateTaskPayload({ + clientKey: context.options.clientKey, + task, + softId: resolvedSoftId, + }), + ), + cancellationController, + ); + debugTask('create task response', response); + + return response; + } catch (err) { + if (err instanceof HttpStatusError) { + throw new CapmonsterCloudClientError(`Cannot create task. Status code was ${err.statusCode}`); + } else if (err instanceof JSONParseError) { + throw new CapmonsterCloudClientError(`Cannot parse create task response. Response was: ${err.responseBody}`); + } + throw err; + } +} + +export async function getTaskResultRequest( + context: TaskOperationContext, + taskId: number, + cancellationController?: AbortController, +): Promise> { + try { + const response = await context.httpClient.post>( + 'getTaskResult', + JSON.stringify(serializeGetTaskResultPayload({ clientKey: context.options.clientKey, taskId })), + cancellationController, + ); + debugTask('GetTaskResult() response', response); + + if (response.errorId !== 0) { + if (response.errorCode.includes('CAPTCHA_NOT_READY')) { + return { type: TaskResultType.InProgress }; + } else { + return { type: TaskResultType.Failed, error: ErrorCodeConverter.convert(response.errorCode) }; + } + } + + if (response.status === TaskResultStatus.ready) { + return { type: TaskResultType.Completed, solution: response.solution as S }; + } + + return { type: TaskResultType.InProgress }; + } catch (err) { + if (err instanceof HttpStatusError) { + if (err.statusCode === HttpStatusCode.ServiceUnavailable) { + return { type: TaskResultType.InProgress }; + } + throw new CapmonsterCloudClientError(`Cannot get task result. Status code was ${err.statusCode}`); + } else if (err instanceof JSONParseError) { + throw new CapmonsterCloudClientError(`Cannot parse get task result response. Response was: ${err.responseBody}`); + } + throw err; + } +} + +export function detectResultTimeoutsOrDefault(task: Task | TaskPayload): GetResultTimeouts { + try { + return detectResultTimeouts(task as Task); + } catch (err) { + return detectResultTimeouts({ type: TaskType.CustomTask } as Task); + } +} + +export async function solveTask( + context: TaskOperationContext, + task: Task | TaskPayload, + resultTimeouts: GetResultTimeouts, + cancellationController?: AbortController, + softId?: number, +): Promise> { + debugTask('task in', task); + debugTask('resultTimeouts in', resultTimeouts); + const createdTask = await createTaskRequest(context, task, cancellationController, softId); + if (createdTask.errorId !== 0) { + return new CaptchaResult({ + error: ErrorCodeConverter.convert((createdTask as CreateTaskResponseError).errorCode), + }); + } + + const firstRequestDelay = task.nocache ? resultTimeouts.firstRequestNoCacheDelay : resultTimeouts.firstRequestDelay; + debugTask('firstRequestDelay', firstRequestDelay); + await new Promise((resolve) => setTimeout(resolve, firstRequestDelay)); + + let signalAborted = (cancellationController && cancellationController.signal.aborted) || false; + const wholeTimeoutId = setTimeout(() => { + signalAborted = true; + cancellationController && cancellationController.abort(); + debugTask('cancellationController abort()'); + }, resultTimeouts.timeout); + + while (signalAborted === false) { + try { + const result = await getTaskResultRequest(context, createdTask.taskId, cancellationController); + switch (result.type) { + case TaskResultType.Failed: + clearTimeout(wholeTimeoutId); + return new CaptchaResult({ error: result.error }); + case TaskResultType.Completed: + clearTimeout(wholeTimeoutId); + return new CaptchaResult({ solution: result.solution as S }); + case TaskResultType.InProgress: + default: + break; + } + } catch (err) { + if (signalAborted) { + break; + } + + clearTimeout(wholeTimeoutId); + throw err; + } + + if (signalAborted) { + break; + } + debugTask('requestsInterval', resultTimeouts.requestsInterval); + await new Promise((resolve) => setTimeout(resolve, resultTimeouts.requestsInterval)); + } + + clearTimeout(wholeTimeoutId); + return new CaptchaResult({ error: ErrorType.Timeout }); +} diff --git a/src/TaskPayload.ts b/src/TaskPayload.ts new file mode 100644 index 0000000..ede1ec9 --- /dev/null +++ b/src/TaskPayload.ts @@ -0,0 +1,367 @@ +import { ProxyType } from './Requests/ProxyType'; + +export type ProxyPayload = { + proxyType?: ProxyType; + proxyAddress?: string; + proxyPort?: number; + proxyLogin?: string; + proxyPassword?: string; +}; + +export type BaseTaskPayload = { + type: TType; + nocache?: boolean; +}; + +export type UnknownTaskPayload = BaseTaskPayload & { + [key: string]: unknown; +}; + +export type RecaptchaV2TaskPayload = BaseTaskPayload<'NoCaptchaTask' | 'NoCaptchaTaskProxyless'> & + ProxyPayload & { + websiteURL: string; + websiteKey: string; + recaptchaDataSValue?: string; + userAgent?: string; + cookies?: string; + isInvisible?: boolean; + }; + +export type RecaptchaV2EnterpriseTaskPayload = BaseTaskPayload<'RecaptchaV2EnterpriseTask'> & + ProxyPayload & { + websiteURL: string; + websiteKey: string; + enterprisePayload?: Record; + recaptchaDataSValue?: string; + userAgent?: string; + cookies?: string; + isInvisible?: boolean; + }; + +export type RecaptchaV3TaskPayload = BaseTaskPayload<'RecaptchaV3TaskProxyless' | 'RecaptchaV3EnterpriseTask'> & { + websiteURL: string; + websiteKey: string; + minScore?: number; + pageAction?: string; + isEnterprise?: boolean | number; +}; + +export type FunCaptchaTaskPayload = BaseTaskPayload<'FunCaptchaTask'> & + ProxyPayload & { + websiteURL: string; + websitePublicKey: string; + data?: string; + funcaptchaApiJSSubdomain?: string; + userAgent?: string; + }; + +export type GeeTestTaskPayload = BaseTaskPayload<'GeeTestTask'> & + ProxyPayload & { + websiteURL: string; + gt: string; + challenge: string; + geetestApiServerSubdomain?: string; + geetestGetLib?: string; + userAgent?: string; + }; + +export type HCaptchaTaskPayload = BaseTaskPayload<'HCaptchaTask' | 'HCaptchaTaskProxyless'> & + ProxyPayload & { + websiteURL: string; + websiteKey: string; + isInvisible?: boolean; + data?: string; + userAgent?: string; + }; + +export type ImageToTextTaskPayload = BaseTaskPayload<'ImageToTextTask'> & { + body: string; + CapMonsterModule?: string; + Case?: boolean; + numeric?: number; + recognizingThreshold?: number; + math?: boolean; +}; + +export type TurnstileCloudflareTaskType = 'token' | 'cf_clearance' | 'wait_room'; + +export type TurnstileBaseTaskPayload = BaseTaskPayload<'TurnstileTask'> & + ProxyPayload & { + websiteURL: string; + websiteKey: string; + userAgent?: string; + pageAction?: string; + data?: string; + pageData?: string; + apiJsUrl?: string; + htmlPageBase64?: string; + }; + +export type TurnstileTaskPayload = + | (TurnstileBaseTaskPayload & { + cloudflareTaskType?: undefined; + }) + | (TurnstileBaseTaskPayload & { + cloudflareTaskType: 'token'; + userAgent: string; + pageAction: string; + data: string; + pageData: string; + }) + | (TurnstileBaseTaskPayload & { + cloudflareTaskType: 'cf_clearance' | 'wait_room'; + userAgent: string; + htmlPageBase64: string; + proxyType: ProxyType; + proxyAddress: string; + proxyPort: number; + proxyLogin: string; + proxyPassword: string; + }); + +export type ComplexImageTaskPayload = BaseTaskPayload<'ComplexImageTask'> & { + class: string; + metadata?: Record; + imageUrls?: Array; + imagesBase64?: Array; + userAgent?: string; + websiteURL?: string; + websiteUrl?: string; +}; + +export type CustomTaskPayload = BaseTaskPayload<'CustomTask'> & + ProxyPayload & { + class: string; + websiteURL?: string; + websiteKey?: string; + userAgent?: string; + data?: string; + metadata?: Record; + }; + +export type AmazonTaskPayload = BaseTaskPayload<'AmazonTask'> & + ProxyPayload & { + websiteURL: string; + websiteKey: string; + challengeScript?: string; + captchaScript?: string; + context?: string; + iv?: string; + userAgent?: string; + }; + +export type BinanceTaskPayload = BaseTaskPayload<'BinanceTask'> & + ProxyPayload & { + websiteURL: string; + websiteKey?: string; + userAgent?: string; + data?: string; + }; + +export type ProsopoTaskPayload = BaseTaskPayload<'ProsopoTask'> & + ProxyPayload & { + websiteURL: string; + websiteKey: string; + userAgent?: string; + }; + +export type YidunTaskPayload = BaseTaskPayload<'YidunTask'> & + ProxyPayload & { + websiteURL: string; + websiteKey: string; + userAgent?: string; + yidunGetLib?: string; + yidunApiServerSubdomain?: string; + }; + +export type MTCaptchaTaskPayload = BaseTaskPayload<'MTCaptchaTask'> & + ProxyPayload & { + websiteURL: string; + websiteKey: string; + userAgent?: string; + }; + +export type KnownTaskPayload = + | RecaptchaV2TaskPayload + | RecaptchaV2EnterpriseTaskPayload + | RecaptchaV3TaskPayload + | FunCaptchaTaskPayload + | GeeTestTaskPayload + | HCaptchaTaskPayload + | ImageToTextTaskPayload + | TurnstileTaskPayload + | ComplexImageTaskPayload + | CustomTaskPayload + | AmazonTaskPayload + | BinanceTaskPayload + | ProsopoTaskPayload + | YidunTaskPayload + | MTCaptchaTaskPayload; + +export type TaskPayload = KnownTaskPayload | UnknownTaskPayload; + +export type LegacyTaskPayload = { + type: string; + nocache?: boolean; + [key: string]: unknown; +}; + +export type CreateTaskPayload = { + task: TaskPayload; + softId?: number; +}; + +export type GetTaskResultPayload = { + taskId: number; +}; + +export class ValidationError extends Error {} + +const allowedProxyTypes: Array = ['http', 'https', 'socks4', 'socks5']; + +function isObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null && Array.isArray(value) === false; +} + +function requireString(task: Record, fieldName: string): void { + if (typeof task[fieldName] !== 'string' || (task[fieldName] as string).length === 0) { + throw new ValidationError(`${fieldName} must be a non-empty string`); + } +} + +function requireNumber(task: Record, fieldName: string): void { + if (typeof task[fieldName] !== 'number' || !Number.isFinite(task[fieldName])) { + throw new ValidationError(`${fieldName} must be a finite number`); + } +} + +function validateProxy(task: Record, required: boolean): void { + if (required) { + requireString(task, 'proxyAddress'); + requireNumber(task, 'proxyPort'); + requireString(task, 'proxyLogin'); + requireString(task, 'proxyPassword'); + } + + if (task.proxyType !== undefined && (typeof task.proxyType !== 'string' || !allowedProxyTypes.includes(task.proxyType as ProxyType))) { + throw new ValidationError('proxyType must be one of: http, https, socks4, socks5'); + } +} + +function validateWebsiteTask(task: Record): void { + requireString(task, 'websiteURL'); + requireString(task, 'websiteKey'); +} + +function validateTurnstileTask(task: Record): void { + validateWebsiteTask(task); + + if (task.cloudflareTaskType === undefined) { + validateProxy(task, false); + return; + } + + if (task.cloudflareTaskType === 'token') { + requireString(task, 'userAgent'); + requireString(task, 'pageAction'); + requireString(task, 'data'); + requireString(task, 'pageData'); + validateProxy(task, false); + return; + } + + if (task.cloudflareTaskType === 'cf_clearance' || task.cloudflareTaskType === 'wait_room') { + requireString(task, 'userAgent'); + requireString(task, 'htmlPageBase64'); + requireString(task, 'proxyType'); + validateProxy(task, true); + return; + } + + throw new ValidationError('cloudflareTaskType must be one of: token, cf_clearance, wait_room'); +} + +function validateKnownTaskPayload(task: Record): void { + switch (task.type) { + case 'NoCaptchaTask': + case 'NoCaptchaTaskProxyless': + case 'RecaptchaV2EnterpriseTask': + case 'HCaptchaTask': + case 'HCaptchaTaskProxyless': + case 'ProsopoTask': + case 'YidunTask': + case 'MTCaptchaTask': + validateWebsiteTask(task); + validateProxy(task, false); + break; + case 'RecaptchaV3TaskProxyless': + case 'RecaptchaV3EnterpriseTask': + validateWebsiteTask(task); + break; + case 'FunCaptchaTask': + requireString(task, 'websiteURL'); + requireString(task, 'websitePublicKey'); + validateProxy(task, false); + break; + case 'GeeTestTask': + requireString(task, 'websiteURL'); + requireString(task, 'gt'); + requireString(task, 'challenge'); + validateProxy(task, false); + break; + case 'ImageToTextTask': + requireString(task, 'body'); + break; + case 'TurnstileTask': + validateTurnstileTask(task); + break; + case 'ComplexImageTask': + requireString(task, 'class'); + break; + case 'CustomTask': + requireString(task, 'class'); + validateProxy(task, false); + break; + case 'AmazonTask': + validateWebsiteTask(task); + validateProxy(task, false); + break; + case 'BinanceTask': + requireString(task, 'websiteURL'); + validateProxy(task, false); + break; + default: + validateProxy(task, false); + break; + } +} + +export function validateTaskPayload(task: unknown): asserts task is TaskPayload { + if (!isObject(task)) { + throw new ValidationError('Task must be an object'); + } + + if (typeof task.type !== 'string' || task.type.length === 0) { + throw new ValidationError('Task type must be a non-empty string'); + } + + validateKnownTaskPayload(task); +} + +export function validateCreateTaskPayload(payload: unknown): asserts payload is CreateTaskPayload { + if (!isObject(payload)) { + throw new ValidationError('Create task payload must be an object'); + } + + validateTaskPayload(payload.task); + + if (payload.softId !== undefined && typeof payload.softId !== 'number') { + throw new ValidationError('softId must be a number'); + } +} + +export function validateTaskId(taskId: unknown): asserts taskId is number { + if (typeof taskId !== 'number' || !Number.isFinite(taskId)) { + throw new ValidationError('taskId must be a finite number'); + } +} diff --git a/src/TaskPayload.u.spec.ts b/src/TaskPayload.u.spec.ts new file mode 100644 index 0000000..310bad7 --- /dev/null +++ b/src/TaskPayload.u.spec.ts @@ -0,0 +1,51 @@ +import { validateCreateTaskPayload, validateTaskPayload, ValidationError } from './TaskPayload'; + +describe('Check unit tests for task payload validation', () => { + it('should validate known task required fields', () => { + expect(() => + validateCreateTaskPayload({ + task: { + type: 'NoCaptchaTaskProxyless', + websiteURL: 'https://example.com', + }, + }), + ).toThrow(ValidationError); + }); + + it('should validate Cloudflare Challenge token required fields', () => { + expect(() => + validateTaskPayload({ + type: 'TurnstileTask', + websiteURL: 'https://example.com', + websiteKey: 'site-key', + cloudflareTaskType: 'token', + userAgent: 'userAgentPlaceholder', + pageAction: 'managed', + data: 'data', + }), + ).toThrow(ValidationError); + }); + + it('should validate Cloudflare Waiting Room proxy fields', () => { + expect(() => + validateTaskPayload({ + type: 'TurnstileTask', + websiteURL: 'https://example.com', + websiteKey: 'site-key', + cloudflareTaskType: 'wait_room', + htmlPageBase64: 'html', + userAgent: 'userAgentPlaceholder', + proxyType: 'http', + }), + ).toThrow(ValidationError); + }); + + it('should allow unknown task types for forward compatibility', () => { + expect(() => + validateTaskPayload({ + type: 'FutureTask', + payload: 'value', + }), + ).not.toThrow(); + }); +}); diff --git a/src/index.ts b/src/index.ts index 1daa19f..7c6a0a2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,6 @@ import { ClientOptions } from './ClientOptions'; +import { CapmonsterCloudClientError } from './ClientError'; +import { CapMonsterCloudClient } from './CapMonsterCloudClient'; import { CapMonsterCloudClientFactory } from './CapMonsterCloudClientFactory'; import { FunCaptchaRequest, FunCaptchaRequestIn } from './Requests/FunCaptchaRequest'; import { GeeTestRequest, GeeTestRequestIn } from './Requests/GeeTestRequest'; @@ -33,10 +35,42 @@ import { HuntRequest, HuntRequestIn } from './Requests/HuntRequest'; import { AltchaRequest, AltchaRequestIn } from './Requests/AltchaRequest'; import { AlibabaRequest, AlibabaRequestIn } from './Requests/AlibabaRequest'; import { FriendlyRequest, FriendlyRequestIn } from './Requests/FriendlyRequest'; +import { + AmazonTaskPayload, + BaseTaskPayload, + BinanceTaskPayload, + ComplexImageTaskPayload, + CreateTaskPayload, + CustomTaskPayload, + FunCaptchaTaskPayload, + GeeTestTaskPayload, + GetTaskResultPayload, + HCaptchaTaskPayload, + ImageToTextTaskPayload, + KnownTaskPayload, + MTCaptchaTaskPayload, + ProsopoTaskPayload, + ProxyPayload, + RecaptchaV2EnterpriseTaskPayload, + RecaptchaV2TaskPayload, + RecaptchaV3TaskPayload, + TaskPayload, + TurnstileBaseTaskPayload, + TurnstileCloudflareTaskType, + TurnstileTaskPayload, + UnknownTaskPayload, + ValidationError, + YidunTaskPayload, +} from './TaskPayload'; +import { CaptchaResult } from './CaptchaResult'; +import { TaskCompletedSolution, TaskResult } from './GetTaskResult'; export default { CapMonsterCloudClientFactory, + CapMonsterCloudClient, ClientOptions, + CapmonsterCloudClientError, + ValidationError, FunCaptchaRequest, GeeTestRequest, HCaptchaRequest, @@ -74,7 +108,37 @@ export { ProxyType, CapMonsterModules, CapMonsterCloudClientFactory, + CapMonsterCloudClient, ClientOptions, + CapmonsterCloudClientError, + CaptchaResult, + AmazonTaskPayload, + BaseTaskPayload, + BinanceTaskPayload, + ComplexImageTaskPayload, + CreateTaskPayload, + CustomTaskPayload, + FunCaptchaTaskPayload, + GeeTestTaskPayload, + GetTaskResultPayload, + HCaptchaTaskPayload, + ImageToTextTaskPayload, + KnownTaskPayload, + MTCaptchaTaskPayload, + ProsopoTaskPayload, + ProxyPayload, + RecaptchaV2EnterpriseTaskPayload, + RecaptchaV2TaskPayload, + RecaptchaV3TaskPayload, + TaskPayload, + TurnstileBaseTaskPayload, + TurnstileCloudflareTaskType, + TurnstileTaskPayload, + TaskCompletedSolution, + TaskResult, + UnknownTaskPayload, + ValidationError, + YidunTaskPayload, FunCaptchaRequest, FunCaptchaRequestIn, GeeTestRequest,