brave-real-browser-mcp-server 2.40.2 → 2.41.1

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.
Binary file
@@ -0,0 +1,525 @@
1
+ /**
2
+ * OCR Text Captcha Solver (Enhanced Version)
3
+ *
4
+ * Simple text-based captcha solver using Tesseract.js OCR
5
+ * Works with captchas like: CCA23E, actukd, hf4kvf (eCourts India style)
6
+ *
7
+ * Features:
8
+ * - Advanced image preprocessing (grayscale, threshold, denoise, remove lines)
9
+ * - Multiple OCR attempts with different settings
10
+ * - Auto-retry with captcha refresh
11
+ * - Hindi + English language support
12
+ * - Works offline (no API needed)
13
+ */
14
+
15
+ const Tesseract = require('tesseract.js');
16
+ const path = require('path');
17
+ const fs = require('fs');
18
+
19
+ // Colors for console
20
+ const colors = {
21
+ green: '\x1b[32m',
22
+ yellow: '\x1b[33m',
23
+ blue: '\x1b[34m',
24
+ red: '\x1b[31m',
25
+ cyan: '\x1b[36m',
26
+ reset: '\x1b[0m'
27
+ };
28
+
29
+ const log = {
30
+ info: (msg) => console.log(`${colors.blue}[ocr-captcha]${colors.reset} ${msg}`),
31
+ success: (msg) => console.log(`${colors.green}[ocr-captcha]${colors.reset} ✅ ${msg}`),
32
+ warn: (msg) => console.log(`${colors.yellow}[ocr-captcha]${colors.reset} ⚠️ ${msg}`),
33
+ error: (msg) => console.log(`${colors.red}[ocr-captcha]${colors.reset} ❌ ${msg}`),
34
+ debug: (msg) => console.log(`${colors.cyan}[ocr-captcha]${colors.reset} 🔍 ${msg}`)
35
+ };
36
+
37
+ // Common captcha character substitutions for correction
38
+ const CHAR_SUBSTITUTIONS = {
39
+ '0': ['O', 'o', 'Q', 'D'],
40
+ 'O': ['0', 'o', 'Q', 'D'],
41
+ '1': ['l', 'I', 'i', '|', '!'],
42
+ 'l': ['1', 'I', 'i', '|'],
43
+ 'I': ['1', 'l', 'i', '|'],
44
+ '5': ['S', 's', '$'],
45
+ 'S': ['5', 's', '$'],
46
+ '8': ['B', '&'],
47
+ 'B': ['8', '&'],
48
+ '2': ['Z', 'z'],
49
+ 'Z': ['2', 'z'],
50
+ '6': ['G', 'b'],
51
+ 'G': ['6', 'C'],
52
+ '9': ['g', 'q'],
53
+ 'g': ['9', 'q'],
54
+ 'C': ['G', 'c', '('],
55
+ 'E': ['3', 'e'],
56
+ '3': ['E', 'e'],
57
+ 'A': ['4', 'a'],
58
+ '4': ['A', 'a'],
59
+ };
60
+
61
+ // Default OCR settings optimized for captchas
62
+ const DEFAULT_OCR_CONFIG = {
63
+ lang: 'eng',
64
+ tessedit_char_whitelist: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
65
+ tessedit_pageseg_mode: '7', // Single line
66
+ preserve_interword_spaces: '0',
67
+ };
68
+
69
+ // Preprocessing configurations for different captcha types
70
+ const PREPROCESS_CONFIGS = [
71
+ { name: 'standard', threshold: 128, invert: false, removeLines: true },
72
+ { name: 'high-contrast', threshold: 100, invert: false, removeLines: true },
73
+ { name: 'low-contrast', threshold: 160, invert: false, removeLines: true },
74
+ { name: 'inverted', threshold: 128, invert: true, removeLines: true },
75
+ { name: 'no-line-removal', threshold: 128, invert: false, removeLines: false },
76
+ ];
77
+
78
+ /**
79
+ * Advanced image preprocessing in browser
80
+ * Removes noise, lines, and enhances text
81
+ */
82
+ async function preprocessImageAdvanced(page, selector, config = {}) {
83
+ const { threshold = 128, invert = false, removeLines = true } = config;
84
+
85
+ return await page.evaluate(({ sel, threshold, invert, removeLines }) => {
86
+ const img = document.querySelector(sel);
87
+ if (!img) return null;
88
+
89
+ const canvas = document.createElement('canvas');
90
+ const ctx = canvas.getContext('2d');
91
+
92
+ // Use natural dimensions for better quality
93
+ const width = img.naturalWidth || img.width || 200;
94
+ const height = img.naturalHeight || img.height || 50;
95
+
96
+ canvas.width = width;
97
+ canvas.height = height;
98
+
99
+ // Draw original image
100
+ ctx.drawImage(img, 0, 0, width, height);
101
+
102
+ // Get image data
103
+ const imageData = ctx.getImageData(0, 0, width, height);
104
+ const data = imageData.data;
105
+
106
+ // Step 1: Convert to grayscale
107
+ for (let i = 0; i < data.length; i += 4) {
108
+ const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
109
+ data[i] = gray;
110
+ data[i + 1] = gray;
111
+ data[i + 2] = gray;
112
+ }
113
+
114
+ // Step 2: Remove diagonal lines (common in captchas)
115
+ if (removeLines) {
116
+ // Detect and remove thin lines
117
+ for (let y = 1; y < height - 1; y++) {
118
+ for (let x = 1; x < width - 1; x++) {
119
+ const idx = (y * width + x) * 4;
120
+ const pixel = data[idx];
121
+
122
+ // Check if this is a dark pixel
123
+ if (pixel < threshold) {
124
+ // Get surrounding pixels
125
+ const top = data[((y - 1) * width + x) * 4];
126
+ const bottom = data[((y + 1) * width + x) * 4];
127
+ const left = data[(y * width + (x - 1)) * 4];
128
+ const right = data[(y * width + (x + 1)) * 4];
129
+
130
+ // Count dark neighbors
131
+ let darkNeighbors = 0;
132
+ if (top < threshold) darkNeighbors++;
133
+ if (bottom < threshold) darkNeighbors++;
134
+ if (left < threshold) darkNeighbors++;
135
+ if (right < threshold) darkNeighbors++;
136
+
137
+ // If isolated or thin line (≤2 dark neighbors), might be noise
138
+ if (darkNeighbors <= 1) {
139
+ // Make it white (remove noise)
140
+ data[idx] = 255;
141
+ data[idx + 1] = 255;
142
+ data[idx + 2] = 255;
143
+ }
144
+ }
145
+ }
146
+ }
147
+ }
148
+
149
+ // Step 3: Apply threshold (binarization)
150
+ for (let i = 0; i < data.length; i += 4) {
151
+ const gray = data[i];
152
+ let bw = gray > threshold ? 255 : 0;
153
+
154
+ // Invert if needed
155
+ if (invert) bw = 255 - bw;
156
+
157
+ data[i] = bw;
158
+ data[i + 1] = bw;
159
+ data[i + 2] = bw;
160
+ }
161
+
162
+ ctx.putImageData(imageData, 0, 0);
163
+
164
+ return canvas.toDataURL('image/png');
165
+ }, { sel: selector, threshold, invert, removeLines });
166
+ }
167
+
168
+ /**
169
+ * Get captcha image with optional preprocessing
170
+ */
171
+ async function getCaptchaImage(page, selector, preprocess = true, preprocessConfig = {}) {
172
+ try {
173
+ if (preprocess) {
174
+ // Try preprocessing first
175
+ const processed = await preprocessImageAdvanced(page, selector, preprocessConfig);
176
+ if (processed) {
177
+ log.debug('Image preprocessed successfully');
178
+ return processed;
179
+ }
180
+ }
181
+
182
+ // Fallback: Screenshot of element
183
+ const element = await page.$(selector);
184
+ if (element) {
185
+ const screenshot = await element.screenshot({ encoding: 'base64' });
186
+ return `data:image/png;base64,${screenshot}`;
187
+ }
188
+
189
+ // Fallback 2: Get image src
190
+ const imgSrc = await page.$eval(selector, (img) => {
191
+ if (img.tagName === 'IMG') {
192
+ const canvas = document.createElement('canvas');
193
+ canvas.width = img.naturalWidth || img.width;
194
+ canvas.height = img.naturalHeight || img.height;
195
+ const ctx = canvas.getContext('2d');
196
+ ctx.drawImage(img, 0, 0);
197
+ return canvas.toDataURL('image/png');
198
+ }
199
+ return img.src || null;
200
+ });
201
+
202
+ return imgSrc;
203
+ } catch (error) {
204
+ log.warn(`Image capture error: ${error.message}`);
205
+ return null;
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Recognize text using Tesseract with multiple attempts
211
+ */
212
+ async function recognizeText(imageData, config = {}) {
213
+ const worker = await Tesseract.createWorker(config.lang || 'eng');
214
+
215
+ try {
216
+ await worker.setParameters({
217
+ tessedit_char_whitelist: config.tessedit_char_whitelist || DEFAULT_OCR_CONFIG.tessedit_char_whitelist,
218
+ tessedit_pageseg_mode: config.tessedit_pageseg_mode || '7',
219
+ });
220
+
221
+ const { data } = await worker.recognize(imageData);
222
+
223
+ // Clean up recognized text
224
+ let text = data.text
225
+ .replace(/\s+/g, '') // Remove whitespace
226
+ .replace(/[^a-zA-Z0-9]/g, ''); // Keep only alphanumeric
227
+
228
+ return {
229
+ text,
230
+ confidence: data.confidence,
231
+ rawText: data.text,
232
+ };
233
+ } finally {
234
+ await worker.terminate();
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Solve text captcha with multiple preprocessing attempts
240
+ */
241
+ async function solveTextCaptcha(page, selector, options = {}) {
242
+ const {
243
+ lang = 'eng',
244
+ retries = 3,
245
+ confidence = 50, // Lowered for better success rate
246
+ allowedChars = null,
247
+ expectedLength = null,
248
+ tryAllPreprocess = true, // Try all preprocessing configs
249
+ } = options;
250
+
251
+ log.info(`Solving text captcha: ${selector}`);
252
+
253
+ try {
254
+ let bestResult = null;
255
+ let allAttempts = [];
256
+
257
+ // Determine which preprocessing configs to try
258
+ const configsToTry = tryAllPreprocess ? PREPROCESS_CONFIGS : [PREPROCESS_CONFIGS[0]];
259
+
260
+ for (const preprocessConfig of configsToTry) {
261
+ log.debug(`Trying preprocess config: ${preprocessConfig.name}`);
262
+
263
+ // Get preprocessed image
264
+ const imageData = await getCaptchaImage(page, selector, true, preprocessConfig);
265
+
266
+ if (!imageData) {
267
+ continue;
268
+ }
269
+
270
+ // Try different PSM modes
271
+ const psmModes = ['7', '8', '13', '6']; // 7=single line, 8=word, 13=raw, 6=block
272
+
273
+ for (let i = 0; i < Math.min(retries, psmModes.length); i++) {
274
+ const config = {
275
+ ...DEFAULT_OCR_CONFIG,
276
+ lang,
277
+ tessedit_pageseg_mode: psmModes[i],
278
+ ...(allowedChars && { tessedit_char_whitelist: allowedChars }),
279
+ };
280
+
281
+ try {
282
+ const result = await recognizeText(imageData, config);
283
+ result.preprocessConfig = preprocessConfig.name;
284
+ result.psmMode = psmModes[i];
285
+ allAttempts.push(result);
286
+
287
+ log.debug(` PSM ${psmModes[i]}: "${result.text}" (${result.confidence.toFixed(1)}%)`);
288
+
289
+ // Check if result meets criteria
290
+ const meetsConfidence = result.confidence >= confidence;
291
+ const meetsLength = !expectedLength || result.text.length === expectedLength;
292
+ const hasText = result.text.length > 0;
293
+
294
+ if (meetsConfidence && meetsLength && hasText) {
295
+ bestResult = result;
296
+ break;
297
+ }
298
+
299
+ // Keep best result so far (prioritize correct length)
300
+ if (hasText) {
301
+ if (!bestResult) {
302
+ bestResult = result;
303
+ } else if (expectedLength) {
304
+ // Prefer correct length
305
+ if (result.text.length === expectedLength && bestResult.text.length !== expectedLength) {
306
+ bestResult = result;
307
+ } else if (result.confidence > bestResult.confidence) {
308
+ bestResult = result;
309
+ }
310
+ } else if (result.confidence > bestResult.confidence) {
311
+ bestResult = result;
312
+ }
313
+ }
314
+ } catch (err) {
315
+ log.debug(` OCR error: ${err.message}`);
316
+ }
317
+ }
318
+
319
+ // Stop if we found a good result
320
+ if (bestResult && bestResult.confidence >= confidence) {
321
+ break;
322
+ }
323
+ }
324
+
325
+ if (bestResult && bestResult.text) {
326
+ log.success(`Solved: "${bestResult.text}" (confidence: ${bestResult.confidence.toFixed(1)}%, config: ${bestResult.preprocessConfig})`);
327
+ return {
328
+ success: true,
329
+ text: bestResult.text,
330
+ confidence: bestResult.confidence,
331
+ attempts: allAttempts.length,
332
+ allAttempts,
333
+ };
334
+ }
335
+
336
+ log.warn('OCR could not recognize text');
337
+ return {
338
+ success: false,
339
+ text: bestResult?.text || '',
340
+ confidence: bestResult?.confidence || 0,
341
+ attempts: allAttempts.length,
342
+ allAttempts,
343
+ };
344
+
345
+ } catch (error) {
346
+ log.error(`Failed to solve captcha: ${error.message}`);
347
+ return {
348
+ success: false,
349
+ text: '',
350
+ confidence: 0,
351
+ error: error.message,
352
+ };
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Solve captcha and fill input field with auto-retry
358
+ */
359
+ async function solveCaptchaAndFill(page, captchaSelector, inputSelector, options = {}) {
360
+ const {
361
+ humanLike = true,
362
+ submitAfter = false,
363
+ submitSelector = 'button[type="submit"], input[type="submit"], button.btn-primary, input.btn',
364
+ refreshSelector = null,
365
+ maxRefreshAttempts = 5, // Increased retries
366
+ minConfidence = 40, // Minimum confidence to try
367
+ expectedLength = null,
368
+ lang = 'eng',
369
+ allowedChars = null,
370
+ waitAfterRefresh = 1500, // Wait after refresh
371
+ waitBeforeType = 500, // Wait before typing
372
+ } = options;
373
+
374
+ let attempts = 0;
375
+ let lastResult = null;
376
+
377
+ log.info(`Starting captcha solve with ${maxRefreshAttempts} max attempts`);
378
+
379
+ while (attempts < maxRefreshAttempts) {
380
+ attempts++;
381
+ log.info(`Attempt ${attempts}/${maxRefreshAttempts}`);
382
+
383
+ // Wait for image to load properly
384
+ await new Promise(r => setTimeout(r, waitBeforeType));
385
+
386
+ // Solve captcha
387
+ const result = await solveTextCaptcha(page, captchaSelector, {
388
+ lang,
389
+ expectedLength,
390
+ allowedChars,
391
+ confidence: minConfidence,
392
+ tryAllPreprocess: attempts <= 2, // Only try all configs on first 2 attempts
393
+ });
394
+
395
+ lastResult = result;
396
+
397
+ // Check if we got a usable result
398
+ if (!result.text || result.text.length === 0) {
399
+ log.warn('No text recognized');
400
+ if (refreshSelector) {
401
+ log.info('Refreshing captcha...');
402
+ try {
403
+ await page.click(refreshSelector);
404
+ await new Promise(r => setTimeout(r, waitAfterRefresh));
405
+ } catch (e) {
406
+ log.warn(`Refresh failed: ${e.message}`);
407
+ }
408
+ }
409
+ continue;
410
+ }
411
+
412
+ // If expected length specified and doesn't match, refresh
413
+ if (expectedLength && result.text.length !== expectedLength) {
414
+ log.warn(`Length mismatch: got ${result.text.length}, expected ${expectedLength}`);
415
+ if (refreshSelector && attempts < maxRefreshAttempts) {
416
+ log.info('Refreshing captcha for better result...');
417
+ await page.click(refreshSelector);
418
+ await new Promise(r => setTimeout(r, waitAfterRefresh));
419
+ continue;
420
+ }
421
+ }
422
+
423
+ // Fill the input
424
+ const input = await page.$(inputSelector);
425
+ if (!input) {
426
+ return { success: false, error: 'Input field not found', attempts };
427
+ }
428
+
429
+ // Clear field
430
+ await page.click(inputSelector, { clickCount: 3 });
431
+ await page.keyboard.press('Backspace');
432
+ await new Promise(r => setTimeout(r, 100));
433
+
434
+ // Type the captcha
435
+ if (humanLike) {
436
+ for (const char of result.text) {
437
+ await page.keyboard.type(char);
438
+ await new Promise(r => setTimeout(r, 30 + Math.random() * 80));
439
+ }
440
+ } else {
441
+ await page.type(inputSelector, result.text);
442
+ }
443
+
444
+ log.success(`Filled captcha: "${result.text}"`);
445
+
446
+ // Submit if requested
447
+ if (submitAfter) {
448
+ try {
449
+ await page.click(submitSelector);
450
+ await new Promise(r => setTimeout(r, 2000));
451
+
452
+ // Check if captcha was wrong
453
+ const stillOnPage = await page.$(captchaSelector);
454
+ const errorVisible = await page.evaluate(() => {
455
+ const errorSelectors = ['.error', '.alert-danger', '.captcha-error', '#captchaError'];
456
+ return errorSelectors.some(sel => {
457
+ const el = document.querySelector(sel);
458
+ return el && el.offsetParent !== null;
459
+ });
460
+ });
461
+
462
+ if ((stillOnPage || errorVisible) && refreshSelector) {
463
+ log.warn('Captcha might be wrong, retrying...');
464
+ await page.click(refreshSelector);
465
+ await new Promise(r => setTimeout(r, waitAfterRefresh));
466
+ continue;
467
+ }
468
+ } catch (e) {
469
+ log.warn(`Submit error: ${e.message}`);
470
+ }
471
+ }
472
+
473
+ return {
474
+ success: true,
475
+ text: result.text,
476
+ confidence: result.confidence,
477
+ attempts,
478
+ };
479
+ }
480
+
481
+ return {
482
+ success: false,
483
+ error: 'Max attempts reached',
484
+ attempts,
485
+ lastResult,
486
+ };
487
+ }
488
+
489
+ /**
490
+ * Solve captcha from URL directly
491
+ */
492
+ async function solveCaptchaFromUrl(imageUrl, options = {}) {
493
+ log.info(`Solving captcha from URL: ${imageUrl}`);
494
+
495
+ try {
496
+ const result = await recognizeText(imageUrl, {
497
+ ...DEFAULT_OCR_CONFIG,
498
+ ...options,
499
+ });
500
+
501
+ return {
502
+ success: result.confidence > 40 && result.text.length > 0,
503
+ text: result.text,
504
+ confidence: result.confidence,
505
+ };
506
+ } catch (error) {
507
+ return {
508
+ success: false,
509
+ error: error.message,
510
+ };
511
+ }
512
+ }
513
+
514
+ // Export functions
515
+ module.exports = {
516
+ solveTextCaptcha,
517
+ solveCaptchaAndFill,
518
+ solveCaptchaFromUrl,
519
+ getCaptchaImage,
520
+ recognizeText,
521
+ preprocessImageAdvanced,
522
+ CHAR_SUBSTITUTIONS,
523
+ DEFAULT_OCR_CONFIG,
524
+ PREPROCESS_CONFIGS,
525
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brave-real-browser-mcp-server",
3
- "version": "2.40.2",
3
+ "version": "2.41.1",
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",
@@ -43,7 +43,10 @@
43
43
  "lint": "echo 'No linting configured'",
44
44
  "build:all": "npm run build --workspaces --if-present",
45
45
  "clean:all": "npm run clean --workspaces --if-present",
46
- "test:all": "npm run test --workspaces --if-present"
46
+ "test:all": "npm run test --workspaces --if-present",
47
+ "postinstall": "node scripts/auto-update-deps.js || true",
48
+ "update-deps": "node scripts/auto-update-deps.js",
49
+ "upstream-patch": "node scripts/upstream-patcher.js"
47
50
  },
48
51
  "keywords": [
49
52
  "mcp-server",
@@ -76,6 +79,7 @@
76
79
  "ghost-cursor": "^1.4.2",
77
80
  "puppeteer-extra": "^3.3.6",
78
81
  "puppeteer-extra-plugin-stealth": "^2.11.2",
82
+ "tesseract.js": "^5.1.1",
79
83
  "tree-kill": "^1.2.2",
80
84
  "vscode-languageserver": "^9.0.1",
81
85
  "vscode-languageserver-textdocument": "^1.0.12",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brave-real-blocker",
3
- "version": "1.16.2",
3
+ "version": "1.17.1",
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",
@@ -66,7 +66,7 @@
66
66
  "@types/node": "^20.0.0",
67
67
  "brave-real-puppeteer-core": "^24.36.NaN.1",
68
68
  "mocha": "^10.4.0",
69
- "puppeteer-core": "^24.36.1",
69
+ "puppeteer-core": ">=24.0.0",
70
70
  "sinon": "^17.0.1",
71
71
  "ts-node": "^10.9.2",
72
72
  "tsup": "^8.0.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brave-real-launcher",
3
- "version": "1.22.2",
3
+ "version": "1.23.1",
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.16.2",
57
+ "brave-real-blocker": "^1.17.1",
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-playwright-core",
3
- "version": "1.58.NaN.1",
3
+ "version": "1.59.1.1",
4
4
  "description": "Brave-optimized Playwright Core (v1.57.0) with comprehensive stealth patches and error stack sanitization",
5
5
  "keywords": [
6
6
  "playwright",
@@ -134,13 +134,13 @@
134
134
  "test-version": "node ./scripts/test-version-management.js"
135
135
  },
136
136
  "dependencies": {
137
- "brave-real-launcher": "^1.22.2",
137
+ "brave-real-launcher": "^1.23.1",
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.1",
143
- "puppeteer-core": "^24.36.1"
142
+ "playwright-core": ">=1.40.0",
143
+ "puppeteer-core": ">=24.0.0"
144
144
  },
145
145
  "devDependencies": {
146
146
  "test": "^3.3.0"
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Auto-Update Dependencies Script
4
+ *
5
+ * यह script npm install के बाद automatically चलता है और:
6
+ * 1. सभी external dependencies को latest version पर update करता है
7
+ * 2. Internal workspace packages को sync रखता है
8
+ * 3. puppeteer-core और playwright-core को latest पर रखता है
9
+ *
10
+ * Usage: Automatically runs via postinstall hook
11
+ */
12
+
13
+ const { execSync } = require('child_process');
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ // Colors for console
18
+ const colors = {
19
+ green: '\x1b[32m',
20
+ yellow: '\x1b[33m',
21
+ blue: '\x1b[34m',
22
+ red: '\x1b[31m',
23
+ reset: '\x1b[0m',
24
+ bold: '\x1b[1m'
25
+ };
26
+
27
+ const log = {
28
+ info: (msg) => console.log(`${colors.blue}[auto-update]${colors.reset} ${msg}`),
29
+ success: (msg) => console.log(`${colors.green}[auto-update]${colors.reset} ${msg}`),
30
+ warn: (msg) => console.log(`${colors.yellow}[auto-update]${colors.reset} ${msg}`),
31
+ error: (msg) => console.log(`${colors.red}[auto-update]${colors.reset} ${msg}`)
32
+ };
33
+
34
+ // Skip if CI environment and SKIP_AUTO_UPDATE is set
35
+ if (process.env.SKIP_AUTO_UPDATE === 'true') {
36
+ log.info('Skipping auto-update (SKIP_AUTO_UPDATE=true)');
37
+ process.exit(0);
38
+ }
39
+
40
+ // Skip during npm publish
41
+ if (process.env.npm_command === 'publish') {
42
+ log.info('Skipping auto-update during publish');
43
+ process.exit(0);
44
+ }
45
+
46
+ // Critical dependencies to always keep updated
47
+ const CRITICAL_DEPS = [
48
+ 'puppeteer-core',
49
+ 'playwright-core',
50
+ '@modelcontextprotocol/sdk'
51
+ ];
52
+
53
+ // Packages to update (external dependencies)
54
+ const PACKAGES_TO_UPDATE = [
55
+ 'ghost-cursor',
56
+ 'puppeteer-extra',
57
+ 'puppeteer-extra-plugin-stealth',
58
+ 'puppeteer-extra-plugin-adblocker',
59
+ '@ghostery/adblocker-puppeteer'
60
+ ];
61
+
62
+ async function getLatestVersion(packageName) {
63
+ try {
64
+ const result = execSync(`npm view ${packageName} version`, {
65
+ encoding: 'utf8',
66
+ stdio: ['pipe', 'pipe', 'pipe']
67
+ }).trim();
68
+ return result;
69
+ } catch (e) {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ async function getCurrentVersion(packageName) {
75
+ try {
76
+ const result = execSync(`npm list ${packageName} --depth=0 --json`, {
77
+ encoding: 'utf8',
78
+ stdio: ['pipe', 'pipe', 'pipe']
79
+ });
80
+ const data = JSON.parse(result);
81
+ return data.dependencies?.[packageName]?.version || null;
82
+ } catch (e) {
83
+ return null;
84
+ }
85
+ }
86
+
87
+ async function updateDependencies() {
88
+ log.info('Checking for dependency updates...');
89
+
90
+ const updates = [];
91
+
92
+ // Check critical dependencies
93
+ for (const pkg of CRITICAL_DEPS) {
94
+ const latest = await getLatestVersion(pkg);
95
+ const current = await getCurrentVersion(pkg);
96
+
97
+ if (latest && current && latest !== current) {
98
+ updates.push({ name: pkg, current, latest, critical: true });
99
+ }
100
+ }
101
+
102
+ // Check other packages
103
+ for (const pkg of PACKAGES_TO_UPDATE) {
104
+ const latest = await getLatestVersion(pkg);
105
+ const current = await getCurrentVersion(pkg);
106
+
107
+ if (latest && current && latest !== current) {
108
+ updates.push({ name: pkg, current, latest, critical: false });
109
+ }
110
+ }
111
+
112
+ if (updates.length === 0) {
113
+ log.success('All dependencies are up to date!');
114
+ return;
115
+ }
116
+
117
+ // Show what will be updated
118
+ log.info(`Found ${updates.length} packages to update:`);
119
+ updates.forEach(u => {
120
+ const icon = u.critical ? '🔴' : '🔵';
121
+ console.log(` ${icon} ${u.name}: ${u.current} → ${u.latest}`);
122
+ });
123
+
124
+ log.info('Run "npm update" to update these packages.');
125
+ }
126
+
127
+ async function updateBasedOnField() {
128
+ log.info('Checking basedOn field updates...');
129
+
130
+ const packagesDir = path.join(__dirname, '..', 'packages');
131
+ const updates = [];
132
+
133
+ // Check brave-real-puppeteer-core
134
+ const puppeteerPkgPath = path.join(packagesDir, 'brave-real-puppeteer-core', 'package.json');
135
+ if (fs.existsSync(puppeteerPkgPath)) {
136
+ const pkg = JSON.parse(fs.readFileSync(puppeteerPkgPath, 'utf8'));
137
+ const basedOn = pkg.brave?.basedOn?.['puppeteer-core'];
138
+ const latestPuppeteer = await getLatestVersion('puppeteer-core');
139
+
140
+ if (basedOn && latestPuppeteer && basedOn !== latestPuppeteer) {
141
+ updates.push({
142
+ package: 'brave-real-puppeteer-core',
143
+ basedOn,
144
+ latest: latestPuppeteer,
145
+ upstream: 'puppeteer-core'
146
+ });
147
+ }
148
+ }
149
+
150
+ // Check brave-real-playwright-core
151
+ const playwrightPkgPath = path.join(packagesDir, 'brave-real-playwright-core', 'package.json');
152
+ if (fs.existsSync(playwrightPkgPath)) {
153
+ const pkg = JSON.parse(fs.readFileSync(playwrightPkgPath, 'utf8'));
154
+ const basedOn = pkg.brave?.basedOn?.['playwright-core'];
155
+ const latestPlaywright = await getLatestVersion('playwright-core');
156
+
157
+ if (basedOn && latestPlaywright && basedOn !== latestPlaywright) {
158
+ updates.push({
159
+ package: 'brave-real-playwright-core',
160
+ basedOn,
161
+ latest: latestPlaywright,
162
+ upstream: 'playwright-core'
163
+ });
164
+ }
165
+ }
166
+
167
+ if (updates.length > 0) {
168
+ log.warn('Upstream updates available:');
169
+ updates.forEach(u => {
170
+ console.log(` ⬆️ ${u.package}: ${u.upstream} ${u.basedOn} → ${u.latest}`);
171
+ });
172
+ log.info('Run "npm run upstream-patch" to update to latest upstream');
173
+ } else {
174
+ log.success('All packages are based on latest upstream versions!');
175
+ }
176
+ }
177
+
178
+ // Main execution
179
+ async function main() {
180
+ console.log(`\n${colors.bold}🔄 Auto-Update Dependencies${colors.reset}\n`);
181
+
182
+ try {
183
+ await updateDependencies();
184
+ await updateBasedOnField();
185
+ console.log('');
186
+ } catch (e) {
187
+ log.error('Auto-update failed: ' + e.message);
188
+ // Don't fail the install
189
+ process.exit(0);
190
+ }
191
+ }
192
+
193
+ main();
@@ -506,13 +506,105 @@ const handlers = {
506
506
  return { success: true, message: 'Browser closed' };
507
507
  },
508
508
 
509
- // 8. Solve Captcha
509
+ // 8. Solve Captcha (Enhanced with OCR for text captchas)
510
510
  async solve_captcha(params = {}) {
511
511
  const { page } = requireBrowser();
512
- const { type = 'auto', timeout = 30000 } = params;
512
+ const {
513
+ type = 'auto',
514
+ timeout = 30000,
515
+ // OCR-specific options
516
+ captchaSelector,
517
+ inputSelector,
518
+ refreshSelector,
519
+ lang = 'eng',
520
+ expectedLength,
521
+ allowedChars,
522
+ maxRetries = 3
523
+ } = params;
513
524
 
514
525
  notifyProgress('solve_captcha', 'started', `Solving ${type} captcha...`);
515
526
 
527
+ // Handle text/image captcha with OCR
528
+ if (type === 'text' || type === 'image') {
529
+ if (!captchaSelector) {
530
+ return { success: false, error: 'captchaSelector is required for text/image captcha' };
531
+ }
532
+
533
+ try {
534
+ // Dynamic import for OCR solver
535
+ const ocrSolver = require('../../lib/ocr-captcha-solver');
536
+
537
+ if (inputSelector) {
538
+ // Solve and fill
539
+ const result = await ocrSolver.solveCaptchaAndFill(page, captchaSelector, inputSelector, {
540
+ lang,
541
+ expectedLength,
542
+ allowedChars,
543
+ refreshSelector,
544
+ maxRefreshAttempts: maxRetries,
545
+ humanLike: true
546
+ });
547
+
548
+ notifyProgress('solve_captcha', result.success ? 'completed' : 'error',
549
+ result.success ? `OCR solved: "${result.text}"` : `OCR failed: ${result.error}`);
550
+
551
+ return {
552
+ success: result.success,
553
+ type: 'ocr',
554
+ text: result.text,
555
+ confidence: result.confidence,
556
+ attempts: result.attempts,
557
+ filled: true
558
+ };
559
+ } else {
560
+ // Just solve (don't fill)
561
+ const result = await ocrSolver.solveTextCaptcha(page, captchaSelector, {
562
+ lang,
563
+ expectedLength,
564
+ allowedChars,
565
+ retries: maxRetries
566
+ });
567
+
568
+ notifyProgress('solve_captcha', result.success ? 'completed' : 'error',
569
+ result.success ? `OCR result: "${result.text}"` : 'OCR failed');
570
+
571
+ return {
572
+ success: result.success,
573
+ type: 'ocr',
574
+ text: result.text,
575
+ confidence: result.confidence,
576
+ attempts: result.attempts,
577
+ filled: false
578
+ };
579
+ }
580
+ } catch (err) {
581
+ notifyProgress('solve_captcha', 'error', `OCR error: ${err.message}`);
582
+ return { success: false, error: err.message, type: 'ocr' };
583
+ }
584
+ }
585
+
586
+ // Auto-detect captcha type
587
+ if (type === 'auto') {
588
+ // Check for text/image captcha first
589
+ const hasTextCaptcha = await page.evaluate(() => {
590
+ const captchaIndicators = [
591
+ 'img[src*="captcha"]',
592
+ 'img[alt*="captcha"]',
593
+ 'img[id*="captcha"]',
594
+ '.captcha-image',
595
+ '#captcha-image',
596
+ 'img[src*="Captcha"]'
597
+ ];
598
+ return captchaIndicators.some(sel => document.querySelector(sel));
599
+ });
600
+
601
+ if (hasTextCaptcha && captchaSelector) {
602
+ // Recursively call with text type
603
+ return this.solve_captcha({ ...params, type: 'text' });
604
+ }
605
+ }
606
+
607
+ // Original Turnstile/reCAPTCHA/hCaptcha handling
516
608
  const start = Date.now();
517
609
  let attempts = 0;
518
610
 
@@ -173,21 +173,34 @@ const TOOLS = [
173
173
  }
174
174
  },
175
175
 
176
- // 8. Solve Captcha
176
+ // 8. Solve Captcha (Enhanced with OCR for text captchas)
177
177
  {
178
178
  name: 'solve_captcha',
179
179
  emoji: '🔓',
180
- description: 'Auto-solve CAPTCHA with AI (Turnstile, reCAPTCHA, hCaptcha)',
181
- descriptionHindi: 'CAPTCHA हल करना (AI-powered)',
180
+ description: 'Auto-solve CAPTCHA with AI (Turnstile, reCAPTCHA, hCaptcha, Text/Image OCR)',
181
+ descriptionHindi: 'CAPTCHA हल करना (AI + OCR powered)',
182
182
  category: 'interaction',
183
183
  requiresBrowser: true,
184
184
  requiresPage: true,
185
185
  inputSchema: {
186
186
  type: 'object',
187
187
  properties: {
188
- type: { type: 'string', enum: ['turnstile', 'recaptcha', 'hcaptcha', 'auto'], default: 'auto' },
188
+ type: {
189
+ type: 'string',
190
+ enum: ['turnstile', 'recaptcha', 'hcaptcha', 'text', 'image', 'auto'],
191
+ default: 'auto',
192
+ description: 'Captcha type: turnstile/recaptcha/hcaptcha (JS-based), text/image (OCR-based), auto (detect)'
193
+ },
189
194
  timeout: { type: 'number', default: 30000 },
190
- aiMode: { type: 'boolean', default: true, description: 'Use AI vision for complex CAPTCHAs' }
195
+ aiMode: { type: 'boolean', default: true, description: 'Use AI vision for complex CAPTCHAs' },
196
+ // OCR-specific options for text/image captchas
197
+ captchaSelector: { type: 'string', description: 'CSS selector for captcha image (required for text/image type)' },
198
+ inputSelector: { type: 'string', description: 'CSS selector for input field to fill result' },
199
+ refreshSelector: { type: 'string', description: 'CSS selector for captcha refresh button' },
200
+ lang: { type: 'string', default: 'eng', description: 'OCR language: eng, hin, eng+hin' },
201
+ expectedLength: { type: 'number', description: 'Expected captcha text length' },
202
+ allowedChars: { type: 'string', description: 'Allowed characters in captcha' },
203
+ maxRetries: { type: 'number', default: 3, description: 'Max refresh attempts for OCR' }
191
204
  }
192
205
  }
193
206
  },