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
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,36 @@
## How to use

1. **Add Node**: Search for **CapMonster Cloud** in your n8n workflow.

![img_4.png](images/img_4.png)

2. **Get your API Key**: Copy it from your [CapMonster Cloud Dashboard](https://dash.capmonster.cloud).

![img_1.png](images/img_1.png)

3. **Add Api Key to node**:
![img.png](images/keyNode.png)
4. Select Task Type:

![img.png](images/selectCaptcha.png)

- Use the Task Type dropdown to choose the kind of captcha you want to solve:
- JSON (Custom Task) – supply any valid CapMonster task JSON.
- Recaptcha V2 / V2 Enterprise / V2 Proxy
- Recaptcha V3 / V3 Enterprise
- Image to Text
- GeeTest
- Cloudflare Turnstile (token, managed challenge, waiting room)
- Complex Image Tasks (click, recognition)
- DataDome, Basilisk, TenDI, Amazon (multiple variants), Binance, Imperva, Prosopo, Temu, Yidun, MTCaptcha, Altcha, FunCaptcha, Castle, TSPD, Hunt
- Some task types support optional proxy settings.
![img_5.png](images/img_5.png)
4. **Customize Payload**:

5. **Customize Payload**:
- For JSON tasks, supply a full CapMonster JSON object without clientKey.
- For built-in task types, fill the provided fields (e.g., websiteURL, websiteKey, userAgent, metadata).
- If the task supports proxies, enable Use Proxy and fill the proxy details.
-
![img_2.png](images/img_2.png)
- Find the exact JSON structure in the [Official Task Documentation](https://docs.capmonster.cloud/docs/captchas/).
```json
Expand All @@ -21,5 +45,5 @@
"websiteKey":"6Lcg7CMUAAAAANphynKgn9YAgA4tQ2KI_iqRyTwd"
}
```
5. **Execution**: The node will automatically:
6. **Execution**: The node will automatically:
- Return the solution (token) once it's ready.
2 changes: 2 additions & 0 deletions credentials/CapmonsterCloudApi.credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
IAuthenticateGeneric,
ICredentialTestRequest,
} from 'n8n-workflow';
import { softId } from '../nodes/CapmonsterCloud/const';

export class CapmonsterCloudApi implements ICredentialType {
name = 'capmonsterCloudApi';
Expand All @@ -29,6 +30,7 @@ export class CapmonsterCloudApi implements ICredentialType {
properties: {
body: {
clientKey: '={{$credentials.apiKey}}',
softId,
},
},
};
Expand Down
Binary file added images/keyNode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/selectCaptcha.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion nodes/CapmonsterCloud/CapmonsterCloud.node.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"resources": {
"primaryDocumentation": [
{
"url": "https://www.npmjs.com/package/n8n-capmonstercloud-node"
"url": "https://www.npmjs.com/package/@zennolab_com/n8n-nodes-capmonstercloud"
}
]
}
Expand Down
128 changes: 63 additions & 65 deletions nodes/CapmonsterCloud/CapmonsterCloud.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ import {
INodeTypeDescription,
NodeOperationError,
IDataObject,
sleep,
} from 'n8n-workflow';

import { request, waitForResult } from './transport/request';
import { taskBuilders } from './tasks';
import allFields from './descriptions';
import { TaskType } from './types';
import { softId } from './const';

type CapmonsterResponse = {
errorId: number;
errorDescription?: string;
Expand All @@ -17,55 +22,14 @@ type CreateTaskResponse = CapmonsterResponse & {
taskId: number;
};

type TaskResultResponse = CapmonsterResponse & {
status: 'processing' | 'ready';
solution?: IDataObject;
};

const request = async (context: IExecuteFunctions, url: string, body: Record<string, unknown>) => {
return context.helpers.httpRequest({
method: 'POST',
url,
body,
json: true,
});
};

const waitForResult = async (
context: IExecuteFunctions,
apiKey: string,
taskId: number,
maxAttempts = 30,
delay = 1000,
): Promise<IDataObject> => {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const result = (await request(context, 'https://api.capmonster.cloud/getTaskResult', {
clientKey: apiKey,
taskId,
})) as TaskResultResponse;

if (result.errorId !== 0) {
throw new NodeOperationError(context.getNode(), result.errorDescription || 'CapMonster error');
}

if (result.status === 'ready') {
return result.solution ?? {};
}

await sleep(delay);
}

throw new NodeOperationError(context.getNode(), 'Timeout: captcha not solved');
};

export class CapmonsterCloud implements INodeType {
description: INodeTypeDescription = {
displayName: 'CapMonster Cloud',
name: 'capmonsterCloud',
icon: 'file:favicon.svg',
group: ['transform'],
version: 1,
description: 'Solve captchas with custom JSON using CapMonster Cloud',
description: 'Solve captchas via CapMonster Cloud',
defaults: { name: 'CapMonster Cloud' },
inputs: ['main'],
outputs: ['main'],
Expand All @@ -75,18 +39,7 @@ export class CapmonsterCloud implements INodeType {
required: true,
},
],
properties: [
{
displayName: 'Task JSON Payload',
name: 'taskJson',
type: 'string',
typeOptions: { editor: 'codeNodeEditor', rows: 10 },
default:
'{\n "type": "RecaptchaV2Task",\n "websiteURL": "https://example.com",\n "websiteKey": "SITE_KEY"\n}',
required: true,
description: 'CapMonster "task" object. Do not include clientKey.',
},
],
properties: allFields,
usableAsTool: true,
};

Expand All @@ -98,36 +51,81 @@ export class CapmonsterCloud implements INodeType {
try {
const credentials = await this.getCredentials('capmonsterCloudApi');
const apiKey = credentials.apiKey as string;
const raw = this.getNodeParameter('taskJson', i) as string;

const taskType = this.getNodeParameter('taskType', i) as TaskType;

let task: IDataObject;

try {
task = JSON.parse(raw) as IDataObject;
} catch {
throw new NodeOperationError(this.getNode(), 'Invalid JSON in Task Payload', { itemIndex: i });

if (taskType === 'json') {
const raw = this.getNodeParameter('taskJson', i) as string;

try {
task = JSON.parse(raw) as IDataObject;

if (!task.type) {
throw new NodeOperationError(this.getNode(), 'Missing "type" field');
}
} catch (error) {
throw new NodeOperationError(
this.getNode(),
`Invalid JSON: ${(error as Error).message}`,
{ itemIndex: i },
);
}
} else {

const builder = taskBuilders[taskType];

if (!builder) {
throw new NodeOperationError(this.getNode(), `Unsupported task type: ${taskType}`, {
itemIndex: i,
});
}

task = builder.call(this, i);
}


task = Object.fromEntries(
Object.entries(task).filter(([, v]) => v !== undefined && v !== ''),
);

const createTask = (await request(this, 'https://api.capmonster.cloud/createTask', {
clientKey: apiKey,
task,
softId,
})) as CreateTaskResponse;

if (createTask.errorId !== 0) {
throw new NodeOperationError(this.getNode(), createTask.errorDescription || 'CreateTask failed', { itemIndex: i });
throw new NodeOperationError(
this.getNode(),
createTask.errorDescription || 'CreateTask failed',
{ itemIndex: i },
);
}

const solution = await waitForResult(this, apiKey, createTask.taskId);

returnData.push({ json: solution, pairedItem: i });
returnData.push({
json: solution,
pairedItem: i,
});
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ json: { error: (error as Error).message }, pairedItem: i });
returnData.push({
json: { error: (error as Error).message },
pairedItem: i,
});
continue;
}
throw new NodeOperationError(this.getNode(), error as Error, { itemIndex: i });

throw new NodeOperationError(this.getNode(), error as Error, {
itemIndex: i,
});
}
}

return [returnData];
}
}
}
1 change: 1 addition & 0 deletions nodes/CapmonsterCloud/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const softId = 148;
67 changes: 67 additions & 0 deletions nodes/CapmonsterCloud/descriptions/altchaFields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { INodeProperties } from 'n8n-workflow';

export const altchaFields: INodeProperties[] = [
{
displayName: 'Website URL',
name: 'websiteURL',
type: 'string',
required: true,
displayOptions: { show: { taskType: ['altcha'] } },
default: '',
description: 'The URL of the page with Altcha',
},
{
displayName: 'Website Key',
name: 'websiteKey',
type: 'string',
displayOptions: { show: { taskType: ['altcha'] } },
default: '',
description: 'Optional Altcha website key',
},
{
displayName: 'User Agent',
name: 'userAgent',
type: 'string',
required: true,
displayOptions: { show: { taskType: ['altcha'] } },
default:
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36',
description: 'User-Agent header',
},
{
displayName: 'Challenge',
name: 'challenge',
type: 'string',
required: true,
displayOptions: { show: { taskType: ['altcha'] } },
default: '',
description: 'Altcha challenge string',
},
{
displayName: 'Iterations',
name: 'iterations',
type: 'string',
required: true,
displayOptions: { show: { taskType: ['altcha'] } },
default: '',
description: 'Number of hashing iterations',
},
{
displayName: 'Salt',
name: 'salt',
type: 'string',
required: true,
displayOptions: { show: { taskType: ['altcha'] } },
default: '',
description: 'Salt value for challenge',
},
{
displayName: 'Signature',
name: 'signature',
type: 'string',
required: true,
displayOptions: { show: { taskType: ['altcha'] } },
default: '',
description: 'Signature for validation',
},
];
69 changes: 69 additions & 0 deletions nodes/CapmonsterCloud/descriptions/amazonFields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { INodeProperties } from 'n8n-workflow';

export const amazonFields: INodeProperties[] = [
{
displayName: 'Website URL',
name: 'websiteURL',
type: 'string',
required: true,
displayOptions: { show: { taskType: ['amazon'] } },
default: '',
description: 'The URL of the Amazon page with the captcha',
},
{
displayName: 'Website Key',
name: 'websiteKey',
type: 'string',
displayOptions: { show: { taskType: ['amazon'] } },
default: '',
description: 'Captcha site key (if available)',
},
{
displayName: 'User Agent',
name: 'userAgent',
type: 'string',
displayOptions: { show: { taskType: ['amazon'] } },
default: '',
description: 'Optional User-Agent header for the request',
},
{
displayName: 'Captcha Script URL',
name: 'captchaScript',
type: 'string',
displayOptions: { show: { taskType: ['amazon'] } },
default: '',
description: 'URL of the captcha SDK script',
},
{
displayName: 'Challenge Script URL',
name: 'challengeScript',
type: 'string',
displayOptions: { show: { taskType: ['amazon'] } },
default: '',
description: 'URL of the challenge script (if applicable)',
},
{
displayName: 'Context',
name: 'context',
type: 'string',
displayOptions: { show: { taskType: ['amazon'] } },
default: '',
description: 'Optional context string for the captcha',
},
{
displayName: 'IV',
name: 'iv',
type: 'string',
displayOptions: { show: { taskType: ['amazon'] } },
default: '',
description: 'Optional initialization vector for the captcha',
},
{
displayName: 'Cookie Solution',
name: 'cookieSolution',
type: 'boolean',
displayOptions: { show: { taskType: ['amazon'] } },
default: true,
description: 'Whether to solve captcha with cookie solution',
},
];
Loading
Loading