brave-real-browser-mcp-server 2.41.11 → 2.41.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/eng.traineddata +0 -0
- package/package.json +2 -2
- package/packages/brave-real-blocker/package.json +2 -2
- package/packages/brave-real-launcher/package.json +2 -2
- package/packages/brave-real-playwright-core/package.json +1 -1
- package/packages/brave-real-puppeteer-core/package.json +4 -4
- package/src/lsp/capabilities/diagnostics.js +55 -55
- package/src/lsp/languages/en.js +2 -3
- package/src/lsp/languages/hi.js +2 -3
- package/src/mcp/handlers.js +404 -83
- package/src/shared/tools.js +26 -40
package/eng.traineddata
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser-mcp-server",
|
|
3
|
-
"version": "2.41.
|
|
3
|
+
"version": "2.41.12",
|
|
4
4
|
"description": "MCP Server for Brave Real Browser - Puppeteer with Brave Browser, Stealth Mode, Ad Blocker, and Turnstile Auto-Solver for undetectable web automation.",
|
|
5
5
|
"main": "lib/cjs/index.js",
|
|
6
6
|
"module": "lib/esm/index.mjs",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"license": "ISC",
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
77
|
-
"brave-real-puppeteer-core": "^24.37.1-brave.
|
|
77
|
+
"brave-real-puppeteer-core": "^24.37.1-brave.11",
|
|
78
78
|
"ghost-cursor": "^1.4.2",
|
|
79
79
|
"puppeteer-extra": "^3.3.6",
|
|
80
80
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-blocker",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.12",
|
|
4
4
|
"description": "Advanced uBlock Origin management and stealth features for Brave Real Browser",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"@types/adm-zip": "^0.5.5",
|
|
65
65
|
"@types/fs-extra": "^11.0.4",
|
|
66
66
|
"@types/node": "^20.0.0",
|
|
67
|
-
"brave-real-puppeteer-core": "^24.37.1-brave.
|
|
67
|
+
"brave-real-puppeteer-core": "^24.37.1-brave.11",
|
|
68
68
|
"mocha": "^10.4.0",
|
|
69
69
|
"puppeteer-core": ">=24.0.0",
|
|
70
70
|
"sinon": "^17.0.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-launcher",
|
|
3
|
-
"version": "1.23.
|
|
3
|
+
"version": "1.23.12",
|
|
4
4
|
"description": "Launch Brave Browser with ease from node. Based on chrome-launcher with Brave-specific support.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"typescript": "^5.0.0"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"brave-real-blocker": "^1.17.
|
|
57
|
+
"brave-real-blocker": "^1.17.12",
|
|
58
58
|
"escape-string-regexp": "^5.0.0",
|
|
59
59
|
"is-wsl": "^3.1.0",
|
|
60
60
|
"which": "^6.0.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-puppeteer-core",
|
|
3
|
-
"version": "24.37.1-brave.
|
|
3
|
+
"version": "24.37.1-brave.11",
|
|
4
4
|
"description": "🦁 Brave Real-World Optimized Puppeteer & Playwright Core with 1-5ms ultra-fast timing, 50+ professional stealth features, intelligent browser auto-detection, and 100% bot detection bypass. Features cross-platform Brave browser integration, comprehensive anti-detection, and breakthrough performance improvements.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"automation",
|
|
@@ -134,13 +134,13 @@
|
|
|
134
134
|
"test-version": "node ./scripts/test-version-management.js"
|
|
135
135
|
},
|
|
136
136
|
"dependencies": {
|
|
137
|
-
"brave-real-launcher": "^1.23.
|
|
137
|
+
"brave-real-launcher": "^1.23.12",
|
|
138
138
|
"get-east-asian-width": "^1.4.0",
|
|
139
139
|
"yargs": "^18.0.0"
|
|
140
140
|
},
|
|
141
141
|
"optionalDependencies": {
|
|
142
|
-
"playwright-core": "^1.58.
|
|
143
|
-
"puppeteer-core": "^24.37.
|
|
142
|
+
"playwright-core": "^1.58.1",
|
|
143
|
+
"puppeteer-core": "^24.37.1"
|
|
144
144
|
},
|
|
145
145
|
"devDependencies": {
|
|
146
146
|
"test": "^3.3.0"
|
|
@@ -12,38 +12,38 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
12
12
|
const text = document.getText();
|
|
13
13
|
const lines = text.split('\n');
|
|
14
14
|
const diagnostics = [];
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
// State tracking
|
|
17
17
|
let browserInitialized = false;
|
|
18
18
|
let browserClosed = false;
|
|
19
19
|
let currentPage = null;
|
|
20
20
|
let networkRecording = false;
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
// Tool categories
|
|
23
23
|
const browserRequiredTools = [
|
|
24
24
|
'navigate', 'get_content', 'wait', 'click', 'type', 'solve_captcha',
|
|
25
25
|
'random_scroll', 'find_element', 'save_content_as_markdown', 'search_regex',
|
|
26
26
|
'extract_json', 'scrape_meta_tags', 'press_key', 'deep_analysis',
|
|
27
27
|
'network_recorder', 'link_harvester', 'cookie_manager', 'iframe_handler',
|
|
28
|
-
'stream_extractor', 'js_scrape', 'execute_js', 'player_api_hook',
|
|
28
|
+
'stream_extractor', 'js_scrape', 'execute_js', 'player_api_hook',
|
|
29
29
|
'redirect_tracer', 'file_downloader'
|
|
30
30
|
];
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
const pageRequiredTools = [
|
|
33
33
|
'get_content', 'click', 'type', 'random_scroll', 'find_element',
|
|
34
34
|
'save_content_as_markdown', 'search_regex', 'extract_json', 'scrape_meta_tags',
|
|
35
35
|
'deep_analysis', 'link_harvester', 'iframe_handler', 'stream_extractor',
|
|
36
|
-
'js_scrape', 'execute_js', 'player_api_hook', '
|
|
36
|
+
'js_scrape', 'execute_js', 'player_api_hook', 'press_key'
|
|
37
37
|
];
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
// Parse all tool calls with their parameters
|
|
40
40
|
const toolCalls = parseAllToolCalls(text, lines, tools);
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
for (const call of toolCalls) {
|
|
43
43
|
if (diagnostics.length >= maxDiagnostics) break;
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
const tool = tools.find(t => t.name === call.name);
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
// 1. Unknown tool check
|
|
48
48
|
if (!tool) {
|
|
49
49
|
diagnostics.push({
|
|
@@ -56,7 +56,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
56
56
|
});
|
|
57
57
|
continue;
|
|
58
58
|
}
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
// 2. Browser state tracking
|
|
61
61
|
if (call.name === 'browser_init') {
|
|
62
62
|
if (browserInitialized && !browserClosed) {
|
|
@@ -71,7 +71,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
71
71
|
browserInitialized = true;
|
|
72
72
|
browserClosed = false;
|
|
73
73
|
}
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
if (call.name === 'browser_close') {
|
|
76
76
|
if (!browserInitialized) {
|
|
77
77
|
diagnostics.push({
|
|
@@ -85,11 +85,11 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
85
85
|
browserClosed = true;
|
|
86
86
|
currentPage = null;
|
|
87
87
|
}
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
if (call.name === 'navigate') {
|
|
90
90
|
currentPage = call.params?.url || 'page';
|
|
91
91
|
}
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
// 3. Browser required check
|
|
94
94
|
if (browserRequiredTools.includes(call.name) && !browserInitialized) {
|
|
95
95
|
diagnostics.push({
|
|
@@ -100,7 +100,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
100
100
|
code: 'browser-not-init'
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
|
-
|
|
103
|
+
|
|
104
104
|
// 4. Browser closed check
|
|
105
105
|
if (browserRequiredTools.includes(call.name) && browserClosed) {
|
|
106
106
|
diagnostics.push({
|
|
@@ -111,7 +111,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
111
111
|
code: 'browser-closed'
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
|
-
|
|
114
|
+
|
|
115
115
|
// 5. Page required check (navigate must be called first)
|
|
116
116
|
if (pageRequiredTools.includes(call.name) && !currentPage && browserInitialized) {
|
|
117
117
|
diagnostics.push({
|
|
@@ -122,7 +122,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
122
122
|
code: 'no-page-loaded'
|
|
123
123
|
});
|
|
124
124
|
}
|
|
125
|
-
|
|
125
|
+
|
|
126
126
|
// 6. Network recorder state
|
|
127
127
|
if (call.name === 'network_recorder') {
|
|
128
128
|
if (call.params?.action === 'start') {
|
|
@@ -140,7 +140,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
140
140
|
networkRecording = false;
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
|
-
|
|
143
|
+
|
|
144
144
|
// 7. Required parameters check
|
|
145
145
|
if (tool.inputSchema?.required) {
|
|
146
146
|
for (const reqParam of tool.inputSchema.required) {
|
|
@@ -156,12 +156,12 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
-
|
|
159
|
+
|
|
160
160
|
// 8. Parameter validation
|
|
161
161
|
if (call.params && tool.inputSchema?.properties) {
|
|
162
162
|
for (const [paramName, paramValue] of Object.entries(call.params)) {
|
|
163
163
|
const schema = tool.inputSchema.properties[paramName];
|
|
164
|
-
|
|
164
|
+
|
|
165
165
|
// Unknown parameter
|
|
166
166
|
if (!schema) {
|
|
167
167
|
diagnostics.push({
|
|
@@ -174,7 +174,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
174
174
|
});
|
|
175
175
|
continue;
|
|
176
176
|
}
|
|
177
|
-
|
|
177
|
+
|
|
178
178
|
// Type validation
|
|
179
179
|
const typeError = validateParamType(paramValue, schema, paramName);
|
|
180
180
|
if (typeError) {
|
|
@@ -186,7 +186,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
186
186
|
code: 'type-error'
|
|
187
187
|
});
|
|
188
188
|
}
|
|
189
|
-
|
|
189
|
+
|
|
190
190
|
// Enum validation
|
|
191
191
|
if (schema.enum && !schema.enum.includes(paramValue)) {
|
|
192
192
|
diagnostics.push({
|
|
@@ -197,7 +197,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
197
197
|
code: 'invalid-enum'
|
|
198
198
|
});
|
|
199
199
|
}
|
|
200
|
-
|
|
200
|
+
|
|
201
201
|
// URL validation
|
|
202
202
|
if (paramName === 'url' && typeof paramValue === 'string') {
|
|
203
203
|
const urlError = validateUrl(paramValue);
|
|
@@ -211,7 +211,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
211
211
|
});
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
|
-
|
|
214
|
+
|
|
215
215
|
// Selector validation
|
|
216
216
|
if (paramName === 'selector' && typeof paramValue === 'string') {
|
|
217
217
|
const selectorError = validateSelector(paramValue);
|
|
@@ -225,7 +225,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
225
225
|
});
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
|
-
|
|
228
|
+
|
|
229
229
|
// Timeout validation
|
|
230
230
|
if (paramName === 'timeout' && typeof paramValue === 'number') {
|
|
231
231
|
if (paramValue < 0) {
|
|
@@ -246,7 +246,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
246
246
|
});
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
|
-
|
|
249
|
+
|
|
250
250
|
// Delay validation
|
|
251
251
|
if (paramName === 'delay' && typeof paramValue === 'number') {
|
|
252
252
|
if (paramValue < 0) {
|
|
@@ -269,7 +269,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
|
-
|
|
272
|
+
|
|
273
273
|
// 9. Tool-specific validations
|
|
274
274
|
if (call.name === 'type' && call.params?.text === '') {
|
|
275
275
|
diagnostics.push({
|
|
@@ -280,7 +280,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
280
280
|
code: 'empty-text'
|
|
281
281
|
});
|
|
282
282
|
}
|
|
283
|
-
|
|
283
|
+
|
|
284
284
|
if (call.name === 'wait' && call.params?.type === 'timeout') {
|
|
285
285
|
const waitValue = parseInt(call.params?.value);
|
|
286
286
|
if (waitValue > 30000) {
|
|
@@ -293,7 +293,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
293
293
|
});
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
|
-
|
|
296
|
+
|
|
297
297
|
if (call.name === 'execute_js' && call.params?.code) {
|
|
298
298
|
const jsCode = call.params.code;
|
|
299
299
|
if (jsCode.includes('eval(') || jsCode.includes('Function(')) {
|
|
@@ -306,7 +306,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
306
306
|
});
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
|
-
|
|
309
|
+
|
|
310
310
|
if (call.name === 'file_downloader' && call.params?.directory) {
|
|
311
311
|
const dir = call.params.directory;
|
|
312
312
|
if (dir.includes('..') || dir.startsWith('/')) {
|
|
@@ -320,7 +320,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
322
|
}
|
|
323
|
-
|
|
323
|
+
|
|
324
324
|
// 10. Document-level checks
|
|
325
325
|
if (browserInitialized && !browserClosed) {
|
|
326
326
|
diagnostics.push({
|
|
@@ -331,7 +331,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
331
331
|
code: 'missing-cleanup'
|
|
332
332
|
});
|
|
333
333
|
}
|
|
334
|
-
|
|
334
|
+
|
|
335
335
|
if (networkRecording) {
|
|
336
336
|
diagnostics.push({
|
|
337
337
|
severity: DiagnosticSeverity.Information,
|
|
@@ -341,7 +341,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
341
341
|
code: 'network-not-stopped'
|
|
342
342
|
});
|
|
343
343
|
}
|
|
344
|
-
|
|
344
|
+
|
|
345
345
|
// 11. Check for duplicate tool calls (potential bugs)
|
|
346
346
|
const navigateCalls = toolCalls.filter(c => c.name === 'navigate');
|
|
347
347
|
if (navigateCalls.length > 5) {
|
|
@@ -353,7 +353,7 @@ function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
|
353
353
|
code: 'many-navigates'
|
|
354
354
|
});
|
|
355
355
|
}
|
|
356
|
-
|
|
356
|
+
|
|
357
357
|
return diagnostics.slice(0, maxDiagnostics);
|
|
358
358
|
}
|
|
359
359
|
|
|
@@ -364,15 +364,15 @@ function parseAllToolCalls(text, lines, tools) {
|
|
|
364
364
|
const calls = [];
|
|
365
365
|
const toolNames = tools.map(t => t.name);
|
|
366
366
|
const toolRegex = new RegExp(`(${toolNames.join('|')})\\s*\\(\\s*(\\{[^}]*\\})?`, 'g');
|
|
367
|
-
|
|
367
|
+
|
|
368
368
|
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
369
369
|
const line = lines[lineNum];
|
|
370
370
|
let match;
|
|
371
|
-
|
|
371
|
+
|
|
372
372
|
while ((match = toolRegex.exec(line)) !== null) {
|
|
373
373
|
const name = match[1];
|
|
374
374
|
const paramsStr = match[2];
|
|
375
|
-
|
|
375
|
+
|
|
376
376
|
calls.push({
|
|
377
377
|
name,
|
|
378
378
|
params: paramsStr ? parseParams(paramsStr) : null,
|
|
@@ -383,10 +383,10 @@ function parseAllToolCalls(text, lines, tools) {
|
|
|
383
383
|
}
|
|
384
384
|
});
|
|
385
385
|
}
|
|
386
|
-
|
|
386
|
+
|
|
387
387
|
toolRegex.lastIndex = 0;
|
|
388
388
|
}
|
|
389
|
-
|
|
389
|
+
|
|
390
390
|
return calls;
|
|
391
391
|
}
|
|
392
392
|
|
|
@@ -401,7 +401,7 @@ function parseParams(paramsStr) {
|
|
|
401
401
|
.replace(/(\w+)\s*:/g, '"$1":')
|
|
402
402
|
.replace(/,\s*}/g, '}')
|
|
403
403
|
.replace(/\n/g, ' ');
|
|
404
|
-
|
|
404
|
+
|
|
405
405
|
return JSON.parse(jsonStr);
|
|
406
406
|
} catch (e) {
|
|
407
407
|
return null;
|
|
@@ -414,7 +414,7 @@ function parseParams(paramsStr) {
|
|
|
414
414
|
function validateParamType(value, schema, paramName) {
|
|
415
415
|
const expectedType = schema.type;
|
|
416
416
|
const actualType = typeof value;
|
|
417
|
-
|
|
417
|
+
|
|
418
418
|
if (expectedType === 'string' && actualType !== 'string') {
|
|
419
419
|
return `Parameter ${paramName}: Expected string, got ${actualType}`;
|
|
420
420
|
}
|
|
@@ -430,7 +430,7 @@ function validateParamType(value, schema, paramName) {
|
|
|
430
430
|
if (expectedType === 'object' && (actualType !== 'object' || value === null || Array.isArray(value))) {
|
|
431
431
|
return `Parameter ${paramName}: Expected object, got ${actualType}`;
|
|
432
432
|
}
|
|
433
|
-
|
|
433
|
+
|
|
434
434
|
return null;
|
|
435
435
|
}
|
|
436
436
|
|
|
@@ -441,7 +441,7 @@ function validateUrl(url) {
|
|
|
441
441
|
if (!url || url.trim() === '') {
|
|
442
442
|
return 'URL cannot be empty';
|
|
443
443
|
}
|
|
444
|
-
|
|
444
|
+
|
|
445
445
|
try {
|
|
446
446
|
const parsed = new URL(url);
|
|
447
447
|
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
@@ -450,7 +450,7 @@ function validateUrl(url) {
|
|
|
450
450
|
} catch (e) {
|
|
451
451
|
return 'Invalid URL format. Example: https://example.com';
|
|
452
452
|
}
|
|
453
|
-
|
|
453
|
+
|
|
454
454
|
return null;
|
|
455
455
|
}
|
|
456
456
|
|
|
@@ -461,20 +461,20 @@ function validateSelector(selector) {
|
|
|
461
461
|
if (!selector || selector.trim() === '') {
|
|
462
462
|
return 'Selector cannot be empty';
|
|
463
463
|
}
|
|
464
|
-
|
|
464
|
+
|
|
465
465
|
// Check for common mistakes
|
|
466
466
|
if (selector.includes(' ')) {
|
|
467
467
|
return 'Selector contains multiple consecutive spaces';
|
|
468
468
|
}
|
|
469
|
-
|
|
469
|
+
|
|
470
470
|
if (/^[0-9]/.test(selector) && !selector.startsWith('[')) {
|
|
471
471
|
return 'Selector cannot start with a number (unless in attribute selector)';
|
|
472
472
|
}
|
|
473
|
-
|
|
473
|
+
|
|
474
474
|
// Check balanced brackets
|
|
475
475
|
const brackets = { '[': ']', '(': ')' };
|
|
476
476
|
const stack = [];
|
|
477
|
-
|
|
477
|
+
|
|
478
478
|
for (const char of selector) {
|
|
479
479
|
if (char in brackets) {
|
|
480
480
|
stack.push(brackets[char]);
|
|
@@ -484,16 +484,16 @@ function validateSelector(selector) {
|
|
|
484
484
|
}
|
|
485
485
|
}
|
|
486
486
|
}
|
|
487
|
-
|
|
487
|
+
|
|
488
488
|
if (stack.length > 0) {
|
|
489
489
|
return 'Unclosed bracket in selector';
|
|
490
490
|
}
|
|
491
|
-
|
|
491
|
+
|
|
492
492
|
// Check for empty attribute selector
|
|
493
493
|
if (selector.includes('[]')) {
|
|
494
494
|
return 'Empty attribute selector []';
|
|
495
495
|
}
|
|
496
|
-
|
|
496
|
+
|
|
497
497
|
return null;
|
|
498
498
|
}
|
|
499
499
|
|
|
@@ -502,7 +502,7 @@ function validateSelector(selector) {
|
|
|
502
502
|
*/
|
|
503
503
|
function findSimilarTools(name, tools) {
|
|
504
504
|
const lowerName = name.toLowerCase();
|
|
505
|
-
|
|
505
|
+
|
|
506
506
|
return tools
|
|
507
507
|
.map(t => ({
|
|
508
508
|
name: t.name,
|
|
@@ -520,14 +520,14 @@ function findSimilarTools(name, tools) {
|
|
|
520
520
|
function calculateSimilarity(s1, s2) {
|
|
521
521
|
const longer = s1.length > s2.length ? s1 : s2;
|
|
522
522
|
const shorter = s1.length > s2.length ? s2 : s1;
|
|
523
|
-
|
|
523
|
+
|
|
524
524
|
if (longer.length === 0) return 1.0;
|
|
525
|
-
|
|
525
|
+
|
|
526
526
|
// Quick check for common prefix
|
|
527
527
|
if (longer.startsWith(shorter) || shorter.startsWith(longer.substring(0, 3))) {
|
|
528
528
|
return 0.8;
|
|
529
529
|
}
|
|
530
|
-
|
|
530
|
+
|
|
531
531
|
// Levenshtein distance
|
|
532
532
|
const costs = [];
|
|
533
533
|
for (let i = 0; i <= s1.length; i++) {
|
|
@@ -546,7 +546,7 @@ function calculateSimilarity(s1, s2) {
|
|
|
546
546
|
}
|
|
547
547
|
if (i > 0) costs[s2.length] = lastValue;
|
|
548
548
|
}
|
|
549
|
-
|
|
549
|
+
|
|
550
550
|
return (longer.length - costs[s2.length]) / longer.length;
|
|
551
551
|
}
|
|
552
552
|
|
package/src/lsp/languages/en.js
CHANGED
|
@@ -21,7 +21,7 @@ module.exports = {
|
|
|
21
21
|
click: { label: 'Click Element', detail: 'Click with human-like behavior', documentation: 'Clicks on an element using ghost-cursor.', parameters: { selector: 'Element selector', humanLike: 'Use realistic movement', clickCount: 'Number of clicks', delay: 'Click delay' } },
|
|
22
22
|
type: { label: 'Type Text', detail: 'Type into input field', documentation: 'Types text with realistic keystroke delays.', parameters: { selector: 'Input selector', text: 'Text to type', delay: 'Keystroke delay', clear: 'Clear first' } },
|
|
23
23
|
browser_close: { label: 'Close Browser', detail: 'Close and cleanup', documentation: 'Closes the browser.', parameters: { force: 'Force close' } },
|
|
24
|
-
solve_captcha: { label: 'Solve CAPTCHA', detail: 'Auto-solve
|
|
24
|
+
solve_captcha: { label: 'Solve CAPTCHA + Form', detail: 'Auto-solve + Fill forms', documentation: 'Solves CAPTCHAs and fills forms with AI field matching.', parameters: { type: 'CAPTCHA type', timeout: 'Max time', formData: 'Form data', submit: 'Submit form' } },
|
|
25
25
|
random_scroll: { label: 'Random Scroll', detail: 'Human-like scrolling', documentation: 'Scrolls with human-like behavior.', parameters: { direction: 'Scroll direction', amount: 'Scroll amount', smooth: 'Smooth scrolling' } },
|
|
26
26
|
find_element: { label: 'Find Element', detail: 'Locate elements', documentation: 'Finds elements by selector/xpath/text.', parameters: { selector: 'CSS selector', xpath: 'XPath', text: 'Text content', multiple: 'Return all' } },
|
|
27
27
|
save_content_as_markdown: { label: 'Save as Markdown', detail: 'Export to MD', documentation: 'Saves page as Markdown file.', parameters: { filename: 'Output filename', selector: 'Content selector', includeImages: 'Include images', includeMeta: 'Include meta' } },
|
|
@@ -40,8 +40,7 @@ module.exports = {
|
|
|
40
40
|
stream_extractor: { label: 'Extract Streams', detail: 'Get video/audio', documentation: 'Extracts stream URLs.', parameters: { types: 'Stream types', quality: 'Quality' } },
|
|
41
41
|
js_scrape: { label: 'JS Scrape', detail: 'Scrape JS content', documentation: 'Scrapes JS-rendered content.', parameters: { selector: 'CSS selector', waitForJS: 'Wait for JS', timeout: 'Max wait' } },
|
|
42
42
|
execute_js: { label: 'Execute JS', detail: 'Run JavaScript', documentation: 'Executes custom JavaScript.', parameters: { code: 'JS code', returnValue: 'Return result' } },
|
|
43
|
-
player_api_hook: { label: 'Player API Hook', detail: 'Video player control', documentation: 'Hooks into video players.', parameters: { playerType: 'Player type', action: 'Action' } }
|
|
44
|
-
form_automator: { label: 'Automate Form', detail: 'Fill and submit', documentation: 'Auto-fills and submits forms.', parameters: { selector: 'Form selector', data: 'Form data', submit: 'Submit', humanLike: 'Human delays' } }
|
|
43
|
+
player_api_hook: { label: 'Player API Hook', detail: 'Video player control', documentation: 'Hooks into video players.', parameters: { playerType: 'Player type', action: 'Action' } }
|
|
45
44
|
},
|
|
46
45
|
diagnostics: {
|
|
47
46
|
browserNotInit: 'Browser not initialized. Call browser_init first.',
|
package/src/lsp/languages/hi.js
CHANGED
|
@@ -11,7 +11,7 @@ module.exports = {
|
|
|
11
11
|
click: { label: 'क्लिक करें', detail: 'मानव-जैसा क्लिक', documentation: 'ghost-cursor से क्लिक करता है।', parameters: { selector: 'एलीमेंट selector', humanLike: 'वास्तविक मूवमेंट', clickCount: 'क्लिक संख्या', delay: 'देरी' } },
|
|
12
12
|
type: { label: 'टाइप करें', detail: 'इनपुट में टाइप', documentation: 'वास्तविक कीस्ट्रोक के साथ टाइप करता है।', parameters: { selector: 'इनपुट selector', text: 'टेक्स्ट', delay: 'कीस्ट्रोक देरी', clear: 'पहले साफ करें' } },
|
|
13
13
|
browser_close: { label: 'ब्राउज़र बंद करें', detail: 'बंद और क्लीनअप', documentation: 'ब्राउज़र बंद करता है।', parameters: { force: 'फोर्स क्लोज़' } },
|
|
14
|
-
solve_captcha: { label: 'CAPTCHA
|
|
14
|
+
solve_captcha: { label: 'CAPTCHA + फॉर्म', detail: 'ऑटो-सॉल्व + फॉर्म भरें', documentation: 'CAPTCHA हल करता है और फॉर्म भी भरता है।', parameters: { type: 'CAPTCHA प्रकार', timeout: 'अधिकतम समय', formData: 'फॉर्म डेटा', submit: 'सबमिट करें' } },
|
|
15
15
|
random_scroll: { label: 'रैंडम स्क्रॉल', detail: 'मानव-जैसा स्क्रॉल', documentation: 'रैंडम स्क्रॉल करता है।', parameters: { direction: 'दिशा', amount: 'मात्रा', smooth: 'स्मूथ' } },
|
|
16
16
|
find_element: { label: 'एलीमेंट खोजें', detail: 'पेज पर खोजें', documentation: 'selector/xpath से खोजता है।', parameters: { selector: 'CSS selector', xpath: 'XPath', text: 'टेक्स्ट', multiple: 'सभी लौटाएं' } },
|
|
17
17
|
save_content_as_markdown: { label: 'MD में सेव', detail: 'Markdown एक्सपोर्ट', documentation: 'Markdown फाइल में सेव करता है।', parameters: { filename: 'फाइलनेम', selector: 'कंटेंट selector', includeImages: 'इमेज शामिल', includeMeta: 'मेटा शामिल' } },
|
|
@@ -30,8 +30,7 @@ module.exports = {
|
|
|
30
30
|
stream_extractor: { label: 'स्ट्रीम निकालें', detail: 'वीडियो/ऑडियो URL', documentation: 'स्ट्रीम URLs निकालता है।', parameters: { types: 'प्रकार', quality: 'क्वालिटी' } },
|
|
31
31
|
js_scrape: { label: 'JS स्क्रैप', detail: 'JS कंटेंट', documentation: 'JS-रेंडर्ड कंटेंट।', parameters: { selector: 'selector', waitForJS: 'JS इंतजार', timeout: 'अधिकतम' } },
|
|
32
32
|
execute_js: { label: 'JS चलाएं', detail: 'JavaScript रन', documentation: 'कस्टम JS चलाता है।', parameters: { code: 'कोड', returnValue: 'रिज़ल्ट लौटाएं' } },
|
|
33
|
-
player_api_hook: { label: 'Player Hook', detail: 'वीडियो प्लेयर', documentation: 'प्लेयर में हुक करता है।', parameters: { playerType: 'प्लेयर', action: 'एक्शन' } }
|
|
34
|
-
form_automator: { label: 'फॉर्म ऑटोमेट', detail: 'फॉर्म भरें', documentation: 'फॉर्म भरता और सबमिट करता है।', parameters: { selector: 'फॉर्म selector', data: 'डेटा', submit: 'सबमिट', humanLike: 'मानव देरी' } }
|
|
33
|
+
player_api_hook: { label: 'Player Hook', detail: 'वीडियो प्लेयर', documentation: 'प्लेयर में हुक करता है।', parameters: { playerType: 'प्लेयर', action: 'एक्शन' } }
|
|
35
34
|
},
|
|
36
35
|
diagnostics: {
|
|
37
36
|
browserNotInit: 'ब्राउज़र इनिशियलाइज़ नहीं। पहले browser_init करें।',
|
package/src/mcp/handlers.js
CHANGED
|
@@ -240,93 +240,242 @@ const decoders = {
|
|
|
240
240
|
* Tool Handlers Object
|
|
241
241
|
*/
|
|
242
242
|
const handlers = {
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
243
|
+
// ═══════════════════════════════════════════════════════════════
|
|
244
|
+
// HELPER: Full Page Analyzer - Detect ALL inputs on page
|
|
245
|
+
// ═══════════════════════════════════════════════════════════════
|
|
246
|
+
async _analyzeFullPage(page) {
|
|
247
|
+
return await page.evaluate(() => {
|
|
248
|
+
const inputs = [];
|
|
249
|
+
const allInputs = document.querySelectorAll('input, textarea, select');
|
|
250
|
+
|
|
251
|
+
allInputs.forEach((el, index) => {
|
|
252
|
+
if (el.type === 'hidden' || el.offsetParent === null) return;
|
|
253
|
+
|
|
254
|
+
// Find associated label
|
|
255
|
+
let label = '';
|
|
256
|
+
if (el.id) {
|
|
257
|
+
const labelEl = document.querySelector(`label[for="${el.id}"]`);
|
|
258
|
+
if (labelEl) label = labelEl.textContent.trim();
|
|
259
|
+
}
|
|
260
|
+
if (!label) {
|
|
261
|
+
const parent = el.closest('label, .form-group, .field');
|
|
262
|
+
if (parent) label = parent.textContent?.split('\n')[0]?.trim() || '';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
inputs.push({
|
|
266
|
+
index,
|
|
267
|
+
tag: el.tagName.toLowerCase(),
|
|
268
|
+
type: el.type || 'text',
|
|
269
|
+
name: el.name || '',
|
|
270
|
+
id: el.id || '',
|
|
271
|
+
placeholder: el.placeholder || '',
|
|
272
|
+
label: label,
|
|
273
|
+
required: el.required,
|
|
274
|
+
value: el.value || '',
|
|
275
|
+
selector: el.id ? `#${el.id}` : (el.name ? `[name="${el.name}"]` : `input[type="${el.type}"]:nth-of-type(${index + 1})`)
|
|
276
|
+
});
|
|
277
|
+
});
|
|
255
278
|
|
|
256
|
-
|
|
279
|
+
// Detect captcha elements
|
|
280
|
+
const captcha = {
|
|
281
|
+
image: document.querySelector('img[src*="captcha"], img[id*="captcha"], .captcha-image')?.src || null,
|
|
282
|
+
input: document.querySelector('input[name*="captcha"], input[id*="captcha"]')?.id || null
|
|
283
|
+
};
|
|
257
284
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const formAnalyzer = require('../../lib/universal-form-analyzer');
|
|
285
|
+
// Detect submit button
|
|
286
|
+
const submitBtn = document.querySelector('button[type="submit"], input[type="submit"], button.submit');
|
|
261
287
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
288
|
+
return {
|
|
289
|
+
inputs,
|
|
290
|
+
captcha,
|
|
291
|
+
submitButton: submitBtn ? (submitBtn.id ? `#${submitBtn.id}` : 'button[type="submit"]') : null,
|
|
292
|
+
totalInputs: inputs.length
|
|
293
|
+
};
|
|
294
|
+
});
|
|
295
|
+
},
|
|
265
296
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
297
|
+
// ═══════════════════════════════════════════════════════════════
|
|
298
|
+
// HELPER: Fill form fields with Sequential Tab Navigation
|
|
299
|
+
// ═══════════════════════════════════════════════════════════════
|
|
300
|
+
async _fillFormFields(page, formData, formSelector, humanLike = true, aiMatch = true) {
|
|
301
|
+
const targetForm = formSelector || 'form';
|
|
302
|
+
const fields = Object.keys(formData || {});
|
|
303
|
+
let filledCount = 0;
|
|
304
|
+
const filledFields = [];
|
|
305
|
+
const unfilledFields = [];
|
|
269
306
|
|
|
270
|
-
|
|
307
|
+
// First, analyze the full page
|
|
308
|
+
const pageInfo = await this._analyzeFullPage(page);
|
|
309
|
+
notifyProgress('solve_captcha', 'progress', `🔍 Page analyzed: ${pageInfo.totalInputs} inputs found`);
|
|
271
310
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
submitAfter: false, // We'll handle submit separately
|
|
277
|
-
});
|
|
311
|
+
for (const [field, value] of Object.entries(formData || {})) {
|
|
312
|
+
// Enhanced AI Field Matching - uses pageInfo for better matching
|
|
313
|
+
let bestMatch = null;
|
|
314
|
+
let bestScore = 0;
|
|
278
315
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
316
|
+
for (const input of pageInfo.inputs) {
|
|
317
|
+
let score = 0;
|
|
318
|
+
const fieldLower = field.toLowerCase();
|
|
319
|
+
|
|
320
|
+
// Exact matches
|
|
321
|
+
if (input.name.toLowerCase() === fieldLower) score = 100;
|
|
322
|
+
else if (input.id.toLowerCase() === fieldLower) score = 95;
|
|
323
|
+
// Partial matches
|
|
324
|
+
else if (input.name.toLowerCase().includes(fieldLower)) score = 80;
|
|
325
|
+
else if (input.id.toLowerCase().includes(fieldLower)) score = 75;
|
|
326
|
+
else if (input.placeholder.toLowerCase().includes(fieldLower)) score = 70;
|
|
327
|
+
else if (input.label.toLowerCase().includes(fieldLower)) score = 65;
|
|
328
|
+
// Type-based matching
|
|
329
|
+
else if (fieldLower.includes('email') && input.type === 'email') score = 60;
|
|
330
|
+
else if (fieldLower.includes('pass') && input.type === 'password') score = 60;
|
|
331
|
+
else if (fieldLower.includes('phone') && input.type === 'tel') score = 60;
|
|
332
|
+
|
|
333
|
+
if (score > bestScore) {
|
|
334
|
+
bestScore = score;
|
|
335
|
+
bestMatch = input;
|
|
336
|
+
}
|
|
282
337
|
}
|
|
283
338
|
|
|
284
|
-
|
|
339
|
+
if (!bestMatch || bestScore < 50) {
|
|
340
|
+
unfilledFields.push(field);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
285
343
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if (!captchaResult.success) {
|
|
292
|
-
return {
|
|
293
|
-
success: false,
|
|
294
|
-
error: 'CAPTCHA solving failed',
|
|
295
|
-
captchaError: captchaResult.error,
|
|
296
|
-
filledFields: result.filledFields
|
|
297
|
-
};
|
|
344
|
+
try {
|
|
345
|
+
const element = await page.$(bestMatch.selector);
|
|
346
|
+
if (!element) {
|
|
347
|
+
unfilledFields.push(field);
|
|
348
|
+
continue;
|
|
298
349
|
}
|
|
299
350
|
|
|
300
|
-
|
|
351
|
+
// Focus element first (human-like)
|
|
352
|
+
await element.focus();
|
|
353
|
+
await new Promise(r => setTimeout(r, 100 + Math.random() * 150));
|
|
354
|
+
|
|
355
|
+
if (bestMatch.tag === 'select') {
|
|
356
|
+
// Smart Select
|
|
357
|
+
await page.evaluate((sel, val) => {
|
|
358
|
+
const el = document.querySelector(sel);
|
|
359
|
+
if (!el) return;
|
|
360
|
+
el.value = val;
|
|
361
|
+
if (el.value !== val) {
|
|
362
|
+
for (const opt of el.options) {
|
|
363
|
+
if (opt.text.toLowerCase().includes(val.toLowerCase())) {
|
|
364
|
+
el.value = opt.value;
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
370
|
+
}, bestMatch.selector, String(value));
|
|
371
|
+
} else if (bestMatch.type === 'checkbox' || bestMatch.type === 'radio') {
|
|
372
|
+
if (value) await element.click();
|
|
373
|
+
} else {
|
|
374
|
+
// Text input - clear and type with human behavior
|
|
375
|
+
await element.click({ clickCount: 3 });
|
|
376
|
+
await page.keyboard.press('Backspace');
|
|
377
|
+
await new Promise(r => setTimeout(r, 50));
|
|
378
|
+
|
|
379
|
+
if (humanLike) {
|
|
380
|
+
// Human-like typing with variable delays
|
|
381
|
+
for (let i = 0; i < String(value).length; i++) {
|
|
382
|
+
const char = String(value)[i];
|
|
383
|
+
await page.keyboard.type(char);
|
|
384
|
+
// Variable delay based on character type
|
|
385
|
+
const delay = char === ' ' ? 80 : (30 + Math.random() * 70);
|
|
386
|
+
await new Promise(r => setTimeout(r, delay));
|
|
387
|
+
}
|
|
388
|
+
} else {
|
|
389
|
+
await page.type(bestMatch.selector, String(value));
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Tab to next field (human-like navigation)
|
|
394
|
+
if (humanLike) {
|
|
395
|
+
await new Promise(r => setTimeout(r, 100 + Math.random() * 200));
|
|
396
|
+
await page.keyboard.press('Tab');
|
|
397
|
+
await new Promise(r => setTimeout(r, 50));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
filledCount++;
|
|
401
|
+
filledFields.push({ field, selector: bestMatch.selector, matchScore: bestScore });
|
|
402
|
+
notifyProgress('solve_captcha', 'progress', `📝 Filled: ${field} (score: ${bestScore})`, { field, filledCount });
|
|
403
|
+
} catch (e) {
|
|
404
|
+
unfilledFields.push(field);
|
|
301
405
|
}
|
|
406
|
+
}
|
|
302
407
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
408
|
+
return {
|
|
409
|
+
success: filledCount > 0,
|
|
410
|
+
filledCount,
|
|
411
|
+
filledFields,
|
|
412
|
+
unfilledFields,
|
|
413
|
+
totalFields: fields.length,
|
|
414
|
+
pageInfo
|
|
415
|
+
};
|
|
416
|
+
},
|
|
417
|
+
|
|
418
|
+
// ═══════════════════════════════════════════════════════════════
|
|
419
|
+
// HELPER: Pre-Submit Validation - Check all required fields filled
|
|
420
|
+
// ═══════════════════════════════════════════════════════════════
|
|
421
|
+
async _validateBeforeSubmit(page) {
|
|
422
|
+
return await page.evaluate(() => {
|
|
423
|
+
const errors = [];
|
|
424
|
+
const requiredFields = document.querySelectorAll('[required], .required input');
|
|
425
|
+
|
|
426
|
+
requiredFields.forEach(field => {
|
|
427
|
+
if (!field.value || field.value.trim() === '') {
|
|
428
|
+
const label = field.name || field.id || field.placeholder || 'Unknown';
|
|
429
|
+
errors.push({ field: label, error: 'Required field is empty' });
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// Check for visible error messages
|
|
434
|
+
const errorMsgs = document.querySelectorAll('.error, .error-message, .invalid-feedback, [class*="error"]');
|
|
435
|
+
errorMsgs.forEach(el => {
|
|
436
|
+
if (el.offsetParent !== null && el.textContent.trim()) {
|
|
437
|
+
errors.push({ field: 'form', error: el.textContent.trim() });
|
|
311
438
|
}
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
return { valid: errors.length === 0, errors };
|
|
442
|
+
});
|
|
443
|
+
},
|
|
444
|
+
|
|
445
|
+
// ═══════════════════════════════════════════════════════════════
|
|
446
|
+
// HELPER: Post-Submit Error Detection
|
|
447
|
+
// ═══════════════════════════════════════════════════════════════
|
|
448
|
+
async _detectPostSubmitErrors(page) {
|
|
449
|
+
await new Promise(r => setTimeout(r, 1500)); // Wait for page response
|
|
450
|
+
|
|
451
|
+
return await page.evaluate(() => {
|
|
452
|
+
const errors = [];
|
|
453
|
+
|
|
454
|
+
// Check for error messages
|
|
455
|
+
const errorSelectors = [
|
|
456
|
+
'.error', '.error-message', '.alert-danger', '.invalid',
|
|
457
|
+
'[class*="error"]', '[class*="invalid"]', '.captcha-error'
|
|
458
|
+
];
|
|
459
|
+
|
|
460
|
+
for (const sel of errorSelectors) {
|
|
461
|
+
document.querySelectorAll(sel).forEach(el => {
|
|
462
|
+
if (el.offsetParent !== null && el.textContent.trim()) {
|
|
463
|
+
errors.push(el.textContent.trim());
|
|
464
|
+
}
|
|
465
|
+
});
|
|
312
466
|
}
|
|
313
467
|
|
|
314
|
-
|
|
468
|
+
// Check if captcha input is still visible (might indicate wrong captcha)
|
|
469
|
+
const captchaInput = document.querySelector('input[name*="captcha"], input[id*="captcha"]');
|
|
470
|
+
if (captchaInput && captchaInput.offsetParent !== null && !captchaInput.value) {
|
|
471
|
+
errors.push('Captcha may have failed - input is empty');
|
|
472
|
+
}
|
|
315
473
|
|
|
316
474
|
return {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
fieldsDetected: forms[0].fields.length,
|
|
320
|
-
fieldsFilled: result.filledFields.length,
|
|
321
|
-
filledFields: result.filledFields,
|
|
322
|
-
captchaSolved: solveCaptcha,
|
|
323
|
-
submitted: submitAfter
|
|
475
|
+
hasErrors: errors.length > 0,
|
|
476
|
+
errors: [...new Set(errors)].slice(0, 5) // Unique errors, max 5
|
|
324
477
|
};
|
|
325
|
-
|
|
326
|
-
} catch (error) {
|
|
327
|
-
notifyProgress('smart_form_filler', 'error', error.message);
|
|
328
|
-
return { success: false, error: error.message };
|
|
329
|
-
}
|
|
478
|
+
});
|
|
330
479
|
},
|
|
331
480
|
|
|
332
481
|
// 1. Browser Init
|
|
@@ -354,6 +503,26 @@ const handlers = {
|
|
|
354
503
|
pageInstance = result.page;
|
|
355
504
|
blockerInstance = result.blocker;
|
|
356
505
|
|
|
506
|
+
// ═══════════════════════════════════════════════════════════════
|
|
507
|
+
// GLOBAL DIALOG HANDLER - Auto-accept all dialogs (alerts/confirms/prompts)
|
|
508
|
+
// This prevents browser from getting stuck on JavaScript dialogs
|
|
509
|
+
// ═══════════════════════════════════════════════════════════════
|
|
510
|
+
pageInstance.on('dialog', async (dialog) => {
|
|
511
|
+
const dialogType = dialog.type();
|
|
512
|
+
const dialogMessage = dialog.message();
|
|
513
|
+
|
|
514
|
+
notifyProgress('browser_init', 'progress',
|
|
515
|
+
`🔔 Auto-accepting ${dialogType}: ${dialogMessage.substring(0, 100)}...`);
|
|
516
|
+
|
|
517
|
+
try {
|
|
518
|
+
await dialog.accept();
|
|
519
|
+
notifyProgress('browser_init', 'progress', `✅ Dialog accepted: ${dialogType}`);
|
|
520
|
+
} catch (e) {
|
|
521
|
+
// Dialog might already be handled
|
|
522
|
+
console.error(`Dialog handling error: ${e.message}`);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
|
|
357
526
|
const pid = browserInstance.process()?.pid;
|
|
358
527
|
|
|
359
528
|
notifyProgress('browser_init', 'completed', `Browser started (PID: ${pid})`, {
|
|
@@ -528,26 +697,54 @@ const handlers = {
|
|
|
528
697
|
// 5. Click
|
|
529
698
|
async click(params) {
|
|
530
699
|
const { page } = requireBrowser();
|
|
531
|
-
const { selector, humanLike = true, clickCount = 1, delay = 0 } = params;
|
|
700
|
+
const { selector, humanLike = true, clickCount = 1, delay = 0, autoAcceptDialogs = true } = params;
|
|
532
701
|
|
|
533
702
|
notifyProgress('click', 'started', `Clicking: ${selector}`);
|
|
534
703
|
|
|
535
|
-
|
|
704
|
+
// Auto-handle dialogs (alerts, confirms, prompts) to prevent blocking
|
|
705
|
+
let dialogHandled = false;
|
|
706
|
+
const dialogHandler = async (dialog) => {
|
|
707
|
+
dialogHandled = true;
|
|
708
|
+
const type = dialog.type();
|
|
709
|
+
const message = dialog.message();
|
|
710
|
+
notifyProgress('click', 'progress', `🔔 Auto-accepting ${type}: ${message.substring(0, 50)}...`);
|
|
536
711
|
try {
|
|
537
|
-
|
|
538
|
-
const cursor = createCursor(page);
|
|
539
|
-
await cursor.click(selector);
|
|
540
|
-
notifyProgress('click', 'progress', 'Used human-like cursor movement');
|
|
712
|
+
await dialog.accept();
|
|
541
713
|
} catch (e) {
|
|
542
|
-
|
|
714
|
+
// Ignore if already handled by global handler
|
|
543
715
|
}
|
|
544
|
-
}
|
|
545
|
-
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
if (autoAcceptDialogs) {
|
|
719
|
+
page.on('dialog', dialogHandler);
|
|
546
720
|
}
|
|
547
721
|
|
|
548
|
-
|
|
722
|
+
try {
|
|
723
|
+
if (humanLike) {
|
|
724
|
+
try {
|
|
725
|
+
const { createCursor } = require('ghost-cursor');
|
|
726
|
+
const cursor = createCursor(page);
|
|
727
|
+
await cursor.click(selector);
|
|
728
|
+
notifyProgress('click', 'progress', 'Used human-like cursor movement');
|
|
729
|
+
} catch (e) {
|
|
730
|
+
await page.click(selector, { clickCount, delay });
|
|
731
|
+
}
|
|
732
|
+
} else {
|
|
733
|
+
await page.click(selector, { clickCount, delay });
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Small wait to allow dialogs to appear and be handled
|
|
737
|
+
await new Promise(r => setTimeout(r, 300));
|
|
549
738
|
|
|
550
|
-
|
|
739
|
+
notifyProgress('click', 'completed', `Clicked: ${selector}${dialogHandled ? ' (dialog auto-accepted)' : ''}`, { selector, humanLike, dialogHandled });
|
|
740
|
+
|
|
741
|
+
return { success: true, selector, clicked: true, dialogHandled };
|
|
742
|
+
} finally {
|
|
743
|
+
// Remove dialog handler to prevent memory leaks
|
|
744
|
+
if (autoAcceptDialogs) {
|
|
745
|
+
page.off('dialog', dialogHandler);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
551
748
|
},
|
|
552
749
|
|
|
553
750
|
// 6. Type
|
|
@@ -596,7 +793,7 @@ const handlers = {
|
|
|
596
793
|
return { success: true, message: 'Browser closed' };
|
|
597
794
|
},
|
|
598
795
|
|
|
599
|
-
// 8. Solve Captcha (
|
|
796
|
+
// 8. Solve Captcha (MERGED with form_automator - handles both CAPTCHA and form automation)
|
|
600
797
|
async solve_captcha(params = {}) {
|
|
601
798
|
const { page } = requireBrowser();
|
|
602
799
|
const {
|
|
@@ -614,9 +811,24 @@ const handlers = {
|
|
|
614
811
|
analyzeFirst = true, // Analyze page before solving
|
|
615
812
|
verifyBeforeSubmit = true, // Verify captcha looks correct before typing
|
|
616
813
|
autoRetry = true, // Auto-retry until success
|
|
814
|
+
// MERGED: Form automation options (from form_automator)
|
|
815
|
+
formData, // Form field data to fill
|
|
816
|
+
formSelector, // Form selector (auto-detect if not provided)
|
|
817
|
+
submit = false, // Auto-submit after filling
|
|
818
|
+
humanLike = true, // Human-like typing delays
|
|
819
|
+
aiMatch = true, // AI matches fields even if names differ
|
|
617
820
|
} = params;
|
|
618
821
|
|
|
619
|
-
|
|
822
|
+
// ═══════════════════════════════════════════════════════════════
|
|
823
|
+
// STEP 0: FORM AUTOMATION (if formData is provided)
|
|
824
|
+
// ═══════════════════════════════════════════════════════════════
|
|
825
|
+
let formResult = null;
|
|
826
|
+
if (formData && Object.keys(formData).length > 0) {
|
|
827
|
+
notifyProgress('solve_captcha', 'started', `📋 Smart Form + Captcha Mode: Filling ${Object.keys(formData).length} fields...`);
|
|
828
|
+
formResult = await this._fillFormFields(page, formData, formSelector, humanLike, aiMatch);
|
|
829
|
+
} else {
|
|
830
|
+
notifyProgress('solve_captcha', 'started', `🎯 100% Accuracy Mode: Solving ${type} captcha...`);
|
|
831
|
+
}
|
|
620
832
|
|
|
621
833
|
// ═══════════════════════════════════════════════════════════════
|
|
622
834
|
// STEP 1: ANALYZE PAGE (if enabled)
|
|
@@ -813,6 +1025,26 @@ const handlers = {
|
|
|
813
1025
|
|
|
814
1026
|
notifyProgress('solve_captcha', 'completed', `✅ 100% VERIFIED: "${finalText}" (${result.confidence.toFixed(0)}%)`);
|
|
815
1027
|
|
|
1028
|
+
// MERGED: Handle form submission if requested
|
|
1029
|
+
if (submit) {
|
|
1030
|
+
notifyProgress('solve_captcha', 'progress', '🚀 Submitting form...');
|
|
1031
|
+
const submitResult = await this._submitForm(page);
|
|
1032
|
+
return {
|
|
1033
|
+
success: true,
|
|
1034
|
+
type: 'ocr',
|
|
1035
|
+
text: finalText,
|
|
1036
|
+
originalText: result.text,
|
|
1037
|
+
confidence: result.confidence,
|
|
1038
|
+
attempts,
|
|
1039
|
+
filled: true,
|
|
1040
|
+
pageAnalysis: analyzeFirst ? pageAnalysis : null,
|
|
1041
|
+
verified: true,
|
|
1042
|
+
formResult,
|
|
1043
|
+
submitted: submitResult.success,
|
|
1044
|
+
submitMessage: submitResult.message
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
|
|
816
1048
|
return {
|
|
817
1049
|
success: true,
|
|
818
1050
|
type: 'ocr',
|
|
@@ -823,6 +1055,7 @@ const handlers = {
|
|
|
823
1055
|
filled: true,
|
|
824
1056
|
pageAnalysis: analyzeFirst ? pageAnalysis : null,
|
|
825
1057
|
verified: true,
|
|
1058
|
+
formResult,
|
|
826
1059
|
};
|
|
827
1060
|
} catch (typeErr) {
|
|
828
1061
|
notifyProgress('solve_captcha', 'error', `Type error: ${typeErr.message}`);
|
|
@@ -839,6 +1072,7 @@ const handlers = {
|
|
|
839
1072
|
filled: false,
|
|
840
1073
|
pageAnalysis: pageAnalysis,
|
|
841
1074
|
verified: true,
|
|
1075
|
+
formResult,
|
|
842
1076
|
};
|
|
843
1077
|
}
|
|
844
1078
|
}
|
|
@@ -850,11 +1084,12 @@ const handlers = {
|
|
|
850
1084
|
error: `Failed after ${attempts} attempts`,
|
|
851
1085
|
lastResult,
|
|
852
1086
|
pageAnalysis,
|
|
1087
|
+
formResult,
|
|
853
1088
|
};
|
|
854
1089
|
|
|
855
1090
|
} catch (err) {
|
|
856
1091
|
notifyProgress('solve_captcha', 'error', `OCR error: ${err.message}`);
|
|
857
|
-
return { success: false, error: err.message, type: 'ocr' };
|
|
1092
|
+
return { success: false, error: err.message, type: 'ocr', formResult };
|
|
858
1093
|
}
|
|
859
1094
|
}
|
|
860
1095
|
|
|
@@ -872,7 +1107,22 @@ const handlers = {
|
|
|
872
1107
|
|
|
873
1108
|
if (turnstileToken) {
|
|
874
1109
|
notifyProgress('solve_captcha', 'completed', `Captcha solved after ${attempts} checks`, { type: 'turnstile', attempts });
|
|
875
|
-
|
|
1110
|
+
|
|
1111
|
+
// MERGED: Handle form submission if requested
|
|
1112
|
+
if (submit) {
|
|
1113
|
+
notifyProgress('solve_captcha', 'progress', '🚀 Submitting form...');
|
|
1114
|
+
const submitResult = await this._submitForm(page);
|
|
1115
|
+
return {
|
|
1116
|
+
success: true,
|
|
1117
|
+
type: 'turnstile',
|
|
1118
|
+
solved: true,
|
|
1119
|
+
formResult,
|
|
1120
|
+
submitted: submitResult.success,
|
|
1121
|
+
submitMessage: submitResult.message
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
return { success: true, type: 'turnstile', solved: true, formResult };
|
|
876
1126
|
}
|
|
877
1127
|
|
|
878
1128
|
if (attempts % 10 === 0) {
|
|
@@ -883,7 +1133,78 @@ const handlers = {
|
|
|
883
1133
|
}
|
|
884
1134
|
|
|
885
1135
|
notifyProgress('solve_captcha', 'error', 'Captcha solving timeout');
|
|
886
|
-
return { success: false, error: 'Captcha solving timeout' };
|
|
1136
|
+
return { success: false, error: 'Captcha solving timeout', formResult };
|
|
1137
|
+
},
|
|
1138
|
+
|
|
1139
|
+
// HELPER: Submit form with smart detection, validation, and error handling
|
|
1140
|
+
async _submitForm(page, validateFirst = true, maxRetries = 1) {
|
|
1141
|
+
try {
|
|
1142
|
+
// Pre-submit validation
|
|
1143
|
+
if (validateFirst) {
|
|
1144
|
+
const validation = await this._validateBeforeSubmit(page);
|
|
1145
|
+
if (!validation.valid) {
|
|
1146
|
+
notifyProgress('solve_captcha', 'warn', `⚠️ Validation failed: ${validation.errors.length} issue(s)`);
|
|
1147
|
+
return { success: false, message: 'Pre-submit validation failed', errors: validation.errors };
|
|
1148
|
+
}
|
|
1149
|
+
notifyProgress('solve_captcha', 'progress', '✅ Pre-submit validation passed');
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
const submitSelector = await page.evaluate(() => {
|
|
1153
|
+
const buttons = Array.from(document.querySelectorAll('button, input[type="submit"], input[type="button"], a.btn'));
|
|
1154
|
+
const candidates = buttons.filter(b => {
|
|
1155
|
+
const text = (b.innerText || b.value || '').toLowerCase();
|
|
1156
|
+
return text.includes('submit') || text.includes('go') || text.includes('search') ||
|
|
1157
|
+
text.includes('view') || text.includes('login') || text.includes('sign in') ||
|
|
1158
|
+
text.includes('register') || text.includes('send');
|
|
1159
|
+
});
|
|
1160
|
+
const best = candidates.find(b => b.offsetParent !== null);
|
|
1161
|
+
if (best) {
|
|
1162
|
+
return best.id ? `#${best.id}` : (best.name ? `[name="${best.name}"]` : 'button[type="submit"]');
|
|
1163
|
+
}
|
|
1164
|
+
// Fallback to any submit button
|
|
1165
|
+
const fallback = document.querySelector('button[type="submit"], input[type="submit"]');
|
|
1166
|
+
return fallback ? (fallback.id ? `#${fallback.id}` : 'button[type="submit"]') : null;
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
if (!submitSelector) {
|
|
1170
|
+
notifyProgress('solve_captcha', 'warn', '⚠️ Could not auto-detect submit button');
|
|
1171
|
+
return { success: false, message: 'Could not auto-detect submit button' };
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// Click submit button with human-like behavior
|
|
1175
|
+
try {
|
|
1176
|
+
const { createCursor } = require('ghost-cursor');
|
|
1177
|
+
const cursor = createCursor(page);
|
|
1178
|
+
await cursor.click(submitSelector);
|
|
1179
|
+
} catch (e) {
|
|
1180
|
+
await page.click(submitSelector);
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// Wait for response
|
|
1184
|
+
try {
|
|
1185
|
+
await page.waitForNavigation({ timeout: 5000, waitUntil: 'domcontentloaded' });
|
|
1186
|
+
notifyProgress('solve_captcha', 'completed', '✅ Form submitted and navigation complete');
|
|
1187
|
+
return { success: true, message: 'Form submitted and navigation complete', navigated: true };
|
|
1188
|
+
} catch (e) {
|
|
1189
|
+
// No navigation - check for errors on same page
|
|
1190
|
+
const postErrors = await this._detectPostSubmitErrors(page);
|
|
1191
|
+
|
|
1192
|
+
if (postErrors.hasErrors) {
|
|
1193
|
+
notifyProgress('solve_captcha', 'warn', `⚠️ Submit detected errors: ${postErrors.errors[0]}`);
|
|
1194
|
+
return {
|
|
1195
|
+
success: false,
|
|
1196
|
+
message: 'Form submitted but errors detected',
|
|
1197
|
+
errors: postErrors.errors,
|
|
1198
|
+
needsRetry: postErrors.errors.some(e => e.toLowerCase().includes('captcha'))
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
notifyProgress('solve_captcha', 'completed', '✅ Form submitted (no navigation detected)');
|
|
1203
|
+
return { success: true, message: 'Form submitted (no navigation detected)', navigated: false };
|
|
1204
|
+
}
|
|
1205
|
+
} catch (error) {
|
|
1206
|
+
return { success: false, message: error.message };
|
|
1207
|
+
}
|
|
887
1208
|
},
|
|
888
1209
|
|
|
889
1210
|
|
package/src/shared/tools.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Brave Real Browser MCP Server - Shared Tool Definitions
|
|
3
3
|
*
|
|
4
|
-
* OPTIMIZED:
|
|
4
|
+
* OPTIMIZED: 22 tools (merged from 28)
|
|
5
5
|
*
|
|
6
6
|
* Merges Applied:
|
|
7
7
|
* - iframe_handler + stream_extractor + player_api_hook → media_extractor
|
|
8
8
|
* - get_content + js_scrape → get_content (enhanced)
|
|
9
9
|
* - search_regex + extract_json + scrape_meta_tags → extract_data
|
|
10
|
+
* - solve_captcha + form_automator → solve_captcha (enhanced)
|
|
10
11
|
*
|
|
11
12
|
* New Features:
|
|
12
13
|
* - URL/Base64/AES Decoders built into media_extractor
|
|
@@ -125,6 +126,7 @@ const TOOLS = [
|
|
|
125
126
|
selector: { type: 'string', description: 'CSS selector (AI auto-heals if element not found)' },
|
|
126
127
|
humanLike: { type: 'boolean', default: true, description: 'Ghost cursor human movement' },
|
|
127
128
|
aiHeal: { type: 'boolean', default: true, description: 'Auto-find alternative selector if broken' },
|
|
129
|
+
autoAcceptDialogs: { type: 'boolean', default: true, description: 'Auto-accept alerts/confirms to prevent blocking' },
|
|
128
130
|
retries: { type: 'number', default: 3 },
|
|
129
131
|
clickCount: { type: 'number', default: 1 },
|
|
130
132
|
delay: { type: 'number', default: 0 }
|
|
@@ -173,34 +175,42 @@ const TOOLS = [
|
|
|
173
175
|
}
|
|
174
176
|
},
|
|
175
177
|
|
|
176
|
-
// 8. Solve Captcha (Enhanced with OCR
|
|
178
|
+
// 8. Solve Captcha (MERGED with form_automator - Enhanced with OCR + Form Automation)
|
|
177
179
|
{
|
|
178
180
|
name: 'solve_captcha',
|
|
179
181
|
emoji: '🔓',
|
|
180
|
-
description: 'Auto-solve CAPTCHA with AI (Turnstile, reCAPTCHA, hCaptcha, Text/Image OCR)',
|
|
181
|
-
descriptionHindi: 'CAPTCHA हल करना (AI + OCR powered)',
|
|
182
|
+
description: 'Auto-solve CAPTCHA with AI + Smart Form Automation (Turnstile, reCAPTCHA, hCaptcha, Text/Image OCR)',
|
|
183
|
+
descriptionHindi: 'CAPTCHA हल करना + फॉर्म भरना (AI + OCR powered)',
|
|
182
184
|
category: 'interaction',
|
|
183
185
|
requiresBrowser: true,
|
|
184
186
|
requiresPage: true,
|
|
185
187
|
inputSchema: {
|
|
186
188
|
type: 'object',
|
|
187
189
|
properties: {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
190
|
+
// === CAPTCHA OPTIONS ===
|
|
191
|
+
type: {
|
|
192
|
+
type: 'string',
|
|
193
|
+
enum: ['turnstile', 'recaptcha', 'hcaptcha', 'text', 'image', 'auto'],
|
|
191
194
|
default: 'auto',
|
|
192
195
|
description: 'Captcha type: turnstile/recaptcha/hcaptcha (JS-based), text/image (OCR-based), auto (detect)'
|
|
193
196
|
},
|
|
194
197
|
timeout: { type: 'number', default: 30000 },
|
|
195
198
|
aiMode: { type: 'boolean', default: true, description: 'Use AI vision for complex CAPTCHAs' },
|
|
196
|
-
// OCR-specific options for text/image captchas
|
|
197
199
|
captchaSelector: { type: 'string', description: 'CSS selector for captcha image (required for text/image type)' },
|
|
198
200
|
inputSelector: { type: 'string', description: 'CSS selector for input field to fill result' },
|
|
199
201
|
refreshSelector: { type: 'string', description: 'CSS selector for captcha refresh button' },
|
|
200
202
|
lang: { type: 'string', default: 'eng', description: 'OCR language: eng, hin, eng+hin' },
|
|
201
203
|
expectedLength: { type: 'number', description: 'Expected captcha text length' },
|
|
202
204
|
allowedChars: { type: 'string', description: 'Allowed characters in captcha' },
|
|
203
|
-
maxRetries: { type: 'number', default: 3, description: 'Max refresh attempts for OCR' }
|
|
205
|
+
maxRetries: { type: 'number', default: 3, description: 'Max refresh attempts for OCR' },
|
|
206
|
+
|
|
207
|
+
// === FORM AUTOMATION OPTIONS (merged from form_automator) ===
|
|
208
|
+
formData: { type: 'object', description: 'Form field data to fill (AI matches fields automatically)' },
|
|
209
|
+
formSelector: { type: 'string', description: 'Form selector (AI auto-detects if not provided)' },
|
|
210
|
+
submit: { type: 'boolean', default: false, description: 'Auto-submit form after filling and captcha solving' },
|
|
211
|
+
humanLike: { type: 'boolean', default: true, description: 'Human-like typing with random delays' },
|
|
212
|
+
aiMatch: { type: 'boolean', default: true, description: 'AI matches fields even if names differ' },
|
|
213
|
+
aiValidate: { type: 'boolean', default: true, description: 'AI validates form before submission' }
|
|
204
214
|
}
|
|
205
215
|
}
|
|
206
216
|
},
|
|
@@ -483,10 +493,10 @@ const TOOLS = [
|
|
|
483
493
|
inputSchema: {
|
|
484
494
|
type: 'object',
|
|
485
495
|
properties: {
|
|
486
|
-
action: {
|
|
487
|
-
type: 'string',
|
|
488
|
-
enum: ['extract', 'list_iframes', 'switch_iframe', 'player_control', 'decode_url', 'batch_extract'],
|
|
489
|
-
default: 'extract'
|
|
496
|
+
action: {
|
|
497
|
+
type: 'string',
|
|
498
|
+
enum: ['extract', 'list_iframes', 'switch_iframe', 'player_control', 'decode_url', 'batch_extract'],
|
|
499
|
+
default: 'extract'
|
|
490
500
|
},
|
|
491
501
|
// For extraction
|
|
492
502
|
types: { type: 'array', items: { type: 'string' }, default: ['all'], description: 'video, audio, hls, dash, download, iframes' },
|
|
@@ -530,30 +540,6 @@ const TOOLS = [
|
|
|
530
540
|
},
|
|
531
541
|
required: ['code']
|
|
532
542
|
}
|
|
533
|
-
},
|
|
534
|
-
|
|
535
|
-
// 23. Form Automator
|
|
536
|
-
{
|
|
537
|
-
name: 'form_automator',
|
|
538
|
-
emoji: '📋',
|
|
539
|
-
description: 'Smart form automation with AI field detection and validation',
|
|
540
|
-
descriptionHindi: 'फॉर्म भरना (AI detection)',
|
|
541
|
-
category: 'interaction',
|
|
542
|
-
requiresBrowser: true,
|
|
543
|
-
requiresPage: true,
|
|
544
|
-
inputSchema: {
|
|
545
|
-
type: 'object',
|
|
546
|
-
properties: {
|
|
547
|
-
selector: { type: 'string', description: 'Form selector (AI auto-detects if not provided)' },
|
|
548
|
-
data: { type: 'object', description: 'Field data (AI matches fields if names differ)' },
|
|
549
|
-
submit: { type: 'boolean', default: false },
|
|
550
|
-
humanLike: { type: 'boolean', default: true },
|
|
551
|
-
aiMatch: { type: 'boolean', default: true, description: 'AI matches fields even if names differ' },
|
|
552
|
-
aiValidate: { type: 'boolean', default: true, description: 'AI validates form before submission' },
|
|
553
|
-
captcha: { type: 'boolean', default: true, description: 'Auto-solve CAPTCHA if present' }
|
|
554
|
-
},
|
|
555
|
-
required: ['data']
|
|
556
|
-
}
|
|
557
543
|
}
|
|
558
544
|
];
|
|
559
545
|
|
|
@@ -586,9 +572,9 @@ const TOOL_DISPLAY = TOOLS.map(t => ({
|
|
|
586
572
|
category: t.category
|
|
587
573
|
}));
|
|
588
574
|
|
|
589
|
-
module.exports = {
|
|
590
|
-
TOOLS,
|
|
591
|
-
TOOL_DISPLAY,
|
|
575
|
+
module.exports = {
|
|
576
|
+
TOOLS,
|
|
577
|
+
TOOL_DISPLAY,
|
|
592
578
|
CATEGORIES,
|
|
593
579
|
getToolByName,
|
|
594
580
|
getToolsByCategory,
|