Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,6 @@ DEBUG=cmc-* node app.js
- [TurnstileRequest](https://zenno.link/doc-turnstile-proxy-en)
- [Yidun](https://zenno.link/doc-yidun-en)
- [Altcha](https://zenno.link/doc-altcha-en)
- [Castle](https://zenno.link/castle-en)
- [TSPD](https://zenno.link/tspd-en)
- [Hunt](https://zenno.link/hunt-en)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zennolab_com/capmonstercloud-client",
"version": "2.5.0",
"version": "2.6.1",
"description": "Official JS client library for https://capmonster.cloud/ captcha recognition service",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
69 changes: 55 additions & 14 deletions src/CapMonsterCloudClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,12 @@ import { AltchaRequest } from './Requests/AltchaRequest';
import { AltchaResponse } from './Responses/AltchaResponse';
import { RecaptchaV3EnterpriseRequest } from './Requests/RecaptchaV3EnterpriseRequest';
import { RecaptchaV3EnterpriseResponse } from './Responses/RecaptchaV3EnterpriseResponse';

type CustomTaskUnion = TemuRequest | BasiliskRequest | ImpervaRequest;

type ResponseForCustomTask<T> = T extends TemuRequest
? TemuResponse
: T extends BasiliskRequest
? BasiliskResponse
: T extends ImpervaRequest
? ImpervaResponse
: never;
import { CastleRequest } from './Requests/CastleRequest';
import { CastleResponse } from './Responses/CastleResponse';
import { TSPDRequest } from './Requests/TSPDRequest';
import { TSPDResponse } from './Responses/TSPDResponse';
import { HuntRequest } from './Requests/HuntRequest';
import { HuntResponse } from './Responses/HuntResponse';

/**
* Base type for capmonster.cloud Client exceptions
Expand Down Expand Up @@ -263,6 +259,42 @@ export class CapMonsterCloudClient {
resultTimeouts?: GetResultTimeouts,
cancellationController?: AbortController,
): Promise<CaptchaResult<AltchaResponse>>;
/**
* Solve Basilisk task
* You will get response within 10 - 180 secs period depending on service workload.
*/
public async Solve(
task: BasiliskRequest,
resultTimeouts?: GetResultTimeouts,
cancellationController?: AbortController,
): Promise<CaptchaResult<BasiliskResponse>>;
/**
* Solve Imperva task
* You will get response within 10 - 180 secs period depending on service workload.
*/
public async Solve(
task: ImpervaRequest,
resultTimeouts?: GetResultTimeouts,
cancellationController?: AbortController,
): Promise<CaptchaResult<ImpervaResponse>>;
/**
* Solve Temu task
* You will get response within 10 - 180 secs period depending on service workload.
*/
public async Solve(
task: TemuRequest,
resultTimeouts?: GetResultTimeouts,
cancellationController?: AbortController,
): Promise<CaptchaResult<TemuResponse>>;
/**
* Solve Castle task
* You will get response within 10 - 180 secs period depending on service workload.
*/
public async Solve(
task: CastleRequest,
resultTimeouts?: GetResultTimeouts,
cancellationController?: AbortController,
): Promise<CaptchaResult<CastleResponse>>;
/**
* Solve TenDI task
* You will get response within 10 - 180 secs period depending on service workload.
Expand All @@ -273,14 +305,23 @@ export class CapMonsterCloudClient {
cancellationController?: AbortController,
): Promise<CaptchaResult<TenDIResponse>>;
/**
* Solve CustomTask task
* Solve Hunt task
* You will get response within 10 - 180 secs period depending on service workload.
*/
public async Solve(
task: HuntRequest,
resultTimeouts?: GetResultTimeouts,
cancellationController?: AbortController,
): Promise<CaptchaResult<HuntResponse>>;
/**
* Solve TSPD task
* You will get response within 10 - 180 secs period depending on service workload.
*/
public async Solve<T extends CustomTaskUnion>(
task: T,
public async Solve(
task: TSPDRequest,
resultTimeouts?: GetResultTimeouts,
cancellationController?: AbortController,
): Promise<CaptchaResult<ResponseForCustomTask<T>>>;
): Promise<CaptchaResult<TSPDResponse>>;
/**
* Solve Binance task
* You will get response within 10 - 180 secs period depending on service workload.
Expand Down
170 changes: 170 additions & 0 deletions src/CapMonsterCloudClientFactory.i.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import { ProsopoRequest } from './Requests/ProsopoRequest';
import { TemuRequest } from './Requests/TemuRequest';
import { YidunRequest } from './Requests/YidunRequest';
import { MTCaptchaRequest } from './Requests/MTCaptchaRequest';
import { CastleRequest } from './Requests/CastleRequest';
import { TSPDRequest } from './Requests/TSPDRequest';
import { HuntRequest } from './Requests/HuntRequest';

const { version } = require('../package.json'); // eslint-disable-line @typescript-eslint/no-var-requires

Expand Down Expand Up @@ -1133,4 +1136,171 @@ describe('Check integration tests for CapMonsterCloudClientFactory()', () => {

expect(await srv.destroy()).toBeUndefined();
});

it('should solve Castle Task', async () => {
expect.assertions(5);

const srv = await createServerMock({
responses: [
{ responseBody: '{"errorId":0,"taskId":1234567}' },
{ responseBody: '{"errorId":0,"status":"ready","solution":{"token":"castle-token"}}' },
],
});

const cmcClient = CapMonsterCloudClientFactory.Create(
new ClientOptions({ clientKey: '<your capmonster.cloud API key>', serviceUrl: `http://localhost:${srv.address.port}` }),
);

const castleRequest = new CastleRequest({
websiteURL: 'https://example.com/castle',
websiteKey: 'pk_test_123',
metadata: {
wUrl: 'https://s.rsg.sc/auth/js/20251234bgef/build/cw.js',
swUrl: 'https://s.rsg.sc/auth/js/20251213bgef/build/csw.js',
count: 1,
},
});

const task = await cmcClient.Solve(castleRequest);

expect(srv.caughtRequests[0]).toHaveProperty(
'body',
'{"clientKey":"<your capmonster.cloud API key>","task":{"type":"CustomTask","websiteURL":"https://example.com/castle","websiteKey":"pk_test_123","metadata":{"wUrl":"https://s.rsg.sc/auth/js/20251234bgef/build/cw.js","swUrl":"https://s.rsg.sc/auth/js/20251213bgef/build/csw.js","count":1},"class":"Castle"},"softId":54}',
);
expect(srv.caughtRequests[1]).toHaveProperty('body', '{"clientKey":"<your capmonster.cloud API key>","taskId":1234567}');
expect(task).toHaveProperty('solution');
expect(task).toHaveProperty('solution.token', 'castle-token');

expect(await srv.destroy()).toBeUndefined();
});

it('should solve Castle Task with Proxy', async () => {
expect.assertions(5);

const srv = await createServerMock({
responses: [
{ responseBody: '{"errorId":0,"taskId":1234567}' },
{ responseBody: '{"errorId":0,"status":"ready","solution":{"token":"castle-token"}}' },
],
});

const cmcClient = CapMonsterCloudClientFactory.Create(
new ClientOptions({ clientKey: '<your capmonster.cloud API key>', serviceUrl: `http://localhost:${srv.address.port}` }),
);

const castleRequest = new CastleRequest({
websiteURL: 'https://example.com/castle',
websiteKey: 'pk_test_123',
metadata: {
wUrl: 'https://s.rsg.sc/auth/js/20251234bgef/build/cw.js',
swUrl: 'https://s.rsg.sc/auth/js/20251213bgef/build/csw.js',
count: 1,
},
proxy: {
proxyType: 'http',
proxyAddress: '8.8.8.8',
proxyPort: 8080,
proxyLogin: 'proxyLoginHere',
proxyPassword: 'proxyPasswordHere',
},
});

const task = await cmcClient.Solve(castleRequest);

expect(srv.caughtRequests[0]).toHaveProperty(
'body',
'{"clientKey":"<your capmonster.cloud API key>","task":{"type":"CustomTask","websiteURL":"https://example.com/castle","websiteKey":"pk_test_123","metadata":{"wUrl":"https://s.rsg.sc/auth/js/20251234bgef/build/cw.js","swUrl":"https://s.rsg.sc/auth/js/20251213bgef/build/csw.js","count":1},"class":"Castle","proxyType":"http","proxyAddress":"8.8.8.8","proxyPort":8080,"proxyLogin":"proxyLoginHere","proxyPassword":"proxyPasswordHere"},"softId":54}',
);
expect(srv.caughtRequests[1]).toHaveProperty('body', '{"clientKey":"<your capmonster.cloud API key>","taskId":1234567}');
expect(task).toHaveProperty('solution');
expect(task).toHaveProperty('solution.token', 'castle-token');

expect(await srv.destroy()).toBeUndefined();
});

it('should solve TSPD Task', async () => {
expect.assertions(5);

const srv = await createServerMock({
responses: [
{ responseBody: '{"errorId":0,"taskId":1234567}' },
{ responseBody: '{"errorId":0,"status":"ready","solution":{"token":"tspd-token"}}' },
],
});

const cmcClient = CapMonsterCloudClientFactory.Create(
new ClientOptions({ clientKey: '<your capmonster.cloud API key>', serviceUrl: `http://localhost:${srv.address.port}` }),
);

const tspdRequest = new TSPDRequest({
websiteURL: 'https://example.com/tspd',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36',
metadata: {
tspdCookie: 'TS386a400d029=example',
htmlPageBase64: 'html-base64',
},
proxy: {
proxyType: 'http',
proxyAddress: '8.8.8.8',
proxyPort: 8080,
proxyLogin: 'proxyLoginHere',
proxyPassword: 'proxyPasswordHere',
},
});

const task = await cmcClient.Solve(tspdRequest);

expect(srv.caughtRequests[0]).toHaveProperty(
'body',
'{"clientKey":"<your capmonster.cloud API key>","task":{"type":"CustomTask","websiteURL":"https://example.com/tspd","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36","metadata":{"tspdCookie":"TS386a400d029=example","htmlPageBase64":"html-base64"},"class":"tspd","proxyType":"http","proxyAddress":"8.8.8.8","proxyPort":8080,"proxyLogin":"proxyLoginHere","proxyPassword":"proxyPasswordHere"},"softId":54}',
);
expect(srv.caughtRequests[1]).toHaveProperty('body', '{"clientKey":"<your capmonster.cloud API key>","taskId":1234567}');
expect(task).toHaveProperty('solution');
expect(task).toHaveProperty('solution.token', 'tspd-token');

expect(await srv.destroy()).toBeUndefined();
});

it('should solve Hunt Task', async () => {
expect.assertions(5);

const srv = await createServerMock({
responses: [
{ responseBody: '{"errorId":0,"taskId":1234567}' },
{ responseBody: '{"errorId":0,"status":"ready","solution":{"token":"hunt-token"}}' },
],
});

const cmcClient = CapMonsterCloudClientFactory.Create(
new ClientOptions({ clientKey: '<your capmonster.cloud API key>', serviceUrl: `http://localhost:${srv.address.port}` }),
);

const huntRequest = new HuntRequest({
websiteURL: 'https://example.com/hunt',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36',
metadata: {
apiGetLib: 'https://www.example.com/hd-api/external/apps/a2157wab1045d68672a63557e0n2a77edbfd15ea/api.js',
data: 'some-data',
},
proxy: {
proxyType: 'http',
proxyAddress: '8.8.8.8',
proxyPort: 8080,
proxyLogin: 'proxyLoginHere',
proxyPassword: 'proxyPasswordHere',
},
});

const task = await cmcClient.Solve(huntRequest);

expect(srv.caughtRequests[0]).toHaveProperty(
'body',
'{"clientKey":"<your capmonster.cloud API key>","task":{"type":"CustomTask","websiteURL":"https://example.com/hunt","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36","metadata":{"apiGetLib":"https://www.example.com/hd-api/external/apps/a2157wab1045d68672a63557e0n2a77edbfd15ea/api.js","data":"some-data"},"class":"HUNT","proxyType":"http","proxyAddress":"8.8.8.8","proxyPort":8080,"proxyLogin":"proxyLoginHere","proxyPassword":"proxyPasswordHere"},"softId":54}',
);
expect(srv.caughtRequests[1]).toHaveProperty('body', '{"clientKey":"<your capmonster.cloud API key>","taskId":1234567}');
expect(task).toHaveProperty('solution');
expect(task).toHaveProperty('solution.token', 'hunt-token');

expect(await srv.destroy()).toBeUndefined();
});
});
9 changes: 9 additions & 0 deletions src/GetResultTimeouts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ export const RecaptchaV3Timeouts = {
timeout: 1000 * 180,
} as GetResultTimeouts;

export const RecaptchaV3EnterpriseTimeouts = {
firstRequestDelay: 1000 * 1,
firstRequestNoCacheDelay: 1000 * 10,
requestsInterval: 1000 * 3,
timeout: 1000 * 180,
} as GetResultTimeouts;

export const ImageToTextTimeouts = {
firstRequestDelay: 350,
requestsInterval: 200,
Expand Down Expand Up @@ -128,6 +135,8 @@ export function detectResultTimeouts(task: Task): GetResultTimeouts {
return RecaptchaV2Timeouts;
case TaskType.RecaptchaV3TaskProxyless:
return RecaptchaV3Timeouts;
case TaskType.RecaptchaV3EnterpriseTask:
return RecaptchaV3EnterpriseTimeouts;
case TaskType.TurnstileTask:
return TurnstileTimeouts;
case TaskType.ComplexImageTask:
Expand Down
8 changes: 7 additions & 1 deletion src/GetTaskResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import { ImpervaResponse } from './Responses/ImpervaResponse';
import { BinanceResponse } from './Responses/BinanceResponse';
import { ComplexImageRecognitionResponse } from './Responses/ComplexImageRecognitionResponse';
import { AltchaResponse } from './Responses/AltchaResponse';
import { CastleResponse } from './Responses/CastleResponse';
import { TSPDResponse } from './Responses/TSPDResponse';
import { HuntResponse } from './Responses/HuntResponse';

export enum TaskResultType {
Failed = 'Failed',
Expand Down Expand Up @@ -47,7 +50,10 @@ export type TaskCompletedSolution =
| ImpervaResponse
| BinanceResponse
| ComplexImageRecognitionResponse
| AltchaResponse;
| AltchaResponse
| CastleResponse
| TSPDResponse
| HuntResponse;

export type TaskCompleted<S extends TaskCompletedSolution> = {
type: TaskResultType.Completed;
Expand Down
2 changes: 2 additions & 0 deletions src/Requests/AltchaRequest/AltchaRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export type AltchaRequestIn = Pick<AltchaRequestBaseIn, Exclude<keyof AltchaRequ
* {@link https://zenno.link/doc-altcha}
*/
export class AltchaRequest extends AltchaRequestBase {
public declare class: 'altcha';

constructor({ proxy, ...argsObj }: AltchaRequestIn) {
super({ type: TaskType.CustomTask, _class: 'altcha', ...argsObj });

Expand Down
2 changes: 2 additions & 0 deletions src/Requests/BasiliskRequest/BasiliskRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export type BasiliskRequestIn = Pick<BasiliskRequestBaseIn, Exclude<keyof Basili
* {@link https://zenno.link/doc-basilisk-en}
*/
export class BasiliskRequest extends BasiliskRequestBase {
public declare class: 'Basilisk';

constructor({ proxy, ...argsObj }: BasiliskRequestIn) {
super({ type: TaskType.CustomTask, _class: 'Basilisk', ...argsObj });

Expand Down
20 changes: 20 additions & 0 deletions src/Requests/CastleRequest/CastleRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { TaskType } from '../../TaskType';
import { CastleRequestBase, CastleRequestBaseIn } from './CastleRequestBase';
import { ProxyInfo, ProxyInfoIn } from '../ProxyInfo';

export type CastleRequestIn = Pick<CastleRequestBaseIn, Exclude<keyof CastleRequestBaseIn, 'type' | '_class'>> & { proxy?: ProxyInfoIn };

/**
* Castle recognition request.
*/
export class CastleRequest extends CastleRequestBase {
public declare class: 'Castle';

constructor({ proxy, ...argsObj }: CastleRequestIn) {
super({ type: TaskType.CustomTask, _class: 'Castle', ...argsObj });

if (proxy) {
Object.assign(this, new ProxyInfo(proxy));
}
}
}
Loading
Loading