brave-real-browser-mcp-server 2.24.3 → 2.24.4
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.
|
@@ -2367,18 +2367,19 @@ export async function handleCloudflareBypass(page, args) {
|
|
|
2367
2367
|
}
|
|
2368
2368
|
/**
|
|
2369
2369
|
* Master tool: Extract direct stream/download URLs
|
|
2370
|
+
* ULTRA POWERFUL: Handles packed JS, JW Player, Video.js, HLS.js, obfuscated scripts
|
|
2370
2371
|
*/
|
|
2371
2372
|
export async function handleStreamExtractor(page, args) {
|
|
2372
|
-
const formats = args.formats || ['mp4', 'mkv', 'm3u8', 'mp3', 'webm'];
|
|
2373
|
+
const formats = args.formats || ['mp4', 'mkv', 'm3u8', 'mp3', 'webm', 'flv', 'avi'];
|
|
2373
2374
|
const maxRedirects = args.maxRedirects || 10;
|
|
2374
2375
|
const directUrls = [];
|
|
2375
2376
|
// Navigate if URL provided
|
|
2376
2377
|
if (args.url) {
|
|
2377
2378
|
await page.goto(args.url, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
2378
2379
|
}
|
|
2379
|
-
// Handle Cloudflare if enabled
|
|
2380
|
+
// Handle Cloudflare if enabled
|
|
2380
2381
|
if (args.bypassCloudflare) {
|
|
2381
|
-
const cfPatterns = ['Checking your browser', 'Just a moment', 'cf-browser-verification'];
|
|
2382
|
+
const cfPatterns = ['Checking your browser', 'Just a moment', 'cf-browser-verification', 'cf_chl_opt'];
|
|
2382
2383
|
const isCloudflare = async () => {
|
|
2383
2384
|
try {
|
|
2384
2385
|
const content = await page.content();
|
|
@@ -2388,72 +2389,212 @@ export async function handleStreamExtractor(page, args) {
|
|
|
2388
2389
|
return false;
|
|
2389
2390
|
}
|
|
2390
2391
|
};
|
|
2391
|
-
// Wait up to 15 seconds for Cloudflare to pass
|
|
2392
2392
|
const startCf = Date.now();
|
|
2393
|
-
while (await isCloudflare() && Date.now() - startCf <
|
|
2393
|
+
while (await isCloudflare() && Date.now() - startCf < 20000) {
|
|
2394
2394
|
await new Promise(r => setTimeout(r, 1000));
|
|
2395
2395
|
}
|
|
2396
2396
|
}
|
|
2397
|
-
// Handle countdown if enabled
|
|
2397
|
+
// Handle countdown if enabled
|
|
2398
2398
|
if (args.waitForCountdown) {
|
|
2399
|
-
const maxWait =
|
|
2399
|
+
const maxWait = 120;
|
|
2400
2400
|
const startTime = Date.now();
|
|
2401
2401
|
while ((Date.now() - startTime) / 1000 < maxWait) {
|
|
2402
2402
|
const hasCountdown = await page.evaluate(() => {
|
|
2403
2403
|
const text = document.body?.innerText || '';
|
|
2404
|
-
return /\d+\s*seconds?|wait\s*\d+|please\s*wait|countdown/gi.test(text);
|
|
2404
|
+
return /\d+\s*seconds?|wait\s*\d+|please\s*wait|countdown|getting link/gi.test(text);
|
|
2405
2405
|
});
|
|
2406
2406
|
if (!hasCountdown)
|
|
2407
2407
|
break;
|
|
2408
2408
|
await new Promise(r => setTimeout(r, 1000));
|
|
2409
2409
|
}
|
|
2410
2410
|
}
|
|
2411
|
-
// Extract
|
|
2412
|
-
const
|
|
2411
|
+
// ULTRA POWERFUL: Extract from all sources
|
|
2412
|
+
const extractedData = await page.evaluate((fmts) => {
|
|
2413
2413
|
const urls = [];
|
|
2414
|
-
const patterns = fmts.map(f => new RegExp(`https?://[^"'\\s]+\\.${f}([?#][^"'\\s]*)?`, 'gi'));
|
|
2415
|
-
// Check page HTML
|
|
2416
2414
|
const html = document.documentElement.innerHTML;
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2415
|
+
// ============================================================
|
|
2416
|
+
// 1. PACKED JS UNPACKING (p,a,c,k,e,d)
|
|
2417
|
+
// ============================================================
|
|
2418
|
+
const unpackPackedJS = (packed) => {
|
|
2419
|
+
try {
|
|
2420
|
+
// Find packed function pattern
|
|
2421
|
+
const match = packed.match(/eval\(function\(p,a,c,k,e,[rd]\)\{[^}]+\}[^)]+\('[^']+'/);
|
|
2422
|
+
if (!match)
|
|
2423
|
+
return '';
|
|
2424
|
+
// Extract the encoded string and dictionary
|
|
2425
|
+
const stringsMatch = packed.match(/'([^']+)'\.split\('\|'\)/);
|
|
2426
|
+
if (!stringsMatch)
|
|
2427
|
+
return '';
|
|
2428
|
+
const dict = stringsMatch[1].split('|');
|
|
2429
|
+
let result = packed;
|
|
2430
|
+
// Replace placeholders with actual values
|
|
2431
|
+
for (let i = 0; i < dict.length; i++) {
|
|
2432
|
+
if (dict[i]) {
|
|
2433
|
+
const base36 = i.toString(36);
|
|
2434
|
+
result = result.replace(new RegExp(`\\b${base36}\\b`, 'g'), dict[i]);
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
return result;
|
|
2438
|
+
}
|
|
2439
|
+
catch {
|
|
2440
|
+
return '';
|
|
2441
|
+
}
|
|
2442
|
+
};
|
|
2443
|
+
// Find and unpack all packed scripts
|
|
2444
|
+
const scripts = document.querySelectorAll('script');
|
|
2445
|
+
scripts.forEach(script => {
|
|
2446
|
+
const content = script.textContent || '';
|
|
2447
|
+
if (content.includes('eval(function(p,a,c,k,e,')) {
|
|
2448
|
+
const unpacked = unpackPackedJS(content);
|
|
2449
|
+
// Extract URLs from unpacked content
|
|
2450
|
+
fmts.forEach(fmt => {
|
|
2451
|
+
const regex = new RegExp(`https?://[^"'\\s]+\\.${fmt}[^"'\\s]*`, 'gi');
|
|
2452
|
+
const matches = unpacked.match(regex);
|
|
2453
|
+
if (matches)
|
|
2454
|
+
matches.forEach(url => urls.push({ url, source: 'packed_js' }));
|
|
2455
|
+
});
|
|
2456
|
+
}
|
|
2421
2457
|
});
|
|
2422
|
-
//
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2458
|
+
// ============================================================
|
|
2459
|
+
// 2. JW PLAYER DETECTION
|
|
2460
|
+
// ============================================================
|
|
2461
|
+
if (window.jwplayer) {
|
|
2462
|
+
try {
|
|
2463
|
+
const player = window.jwplayer();
|
|
2464
|
+
if (player && player.getPlaylistItem) {
|
|
2465
|
+
const item = player.getPlaylistItem();
|
|
2466
|
+
if (item) {
|
|
2467
|
+
if (item.file)
|
|
2468
|
+
urls.push({ url: item.file, source: 'jwplayer' });
|
|
2469
|
+
if (item.sources) {
|
|
2470
|
+
item.sources.forEach((s) => {
|
|
2471
|
+
if (s.file)
|
|
2472
|
+
urls.push({ url: s.file, source: 'jwplayer' });
|
|
2473
|
+
});
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
catch { /* ignore */ }
|
|
2479
|
+
}
|
|
2480
|
+
// JW Player setup patterns in scripts
|
|
2481
|
+
const jwPatterns = [
|
|
2482
|
+
/file:\s*["']([^"']+\.m3u8[^"']*?)["']/gi,
|
|
2483
|
+
/file:\s*["']([^"']+\.mp4[^"']*?)["']/gi,
|
|
2484
|
+
/sources:\s*\[\s*\{[^}]*file:\s*["']([^"']+)["']/gi,
|
|
2485
|
+
/setup\([^)]*file:\s*["']([^"']+)["']/gi,
|
|
2486
|
+
];
|
|
2487
|
+
jwPatterns.forEach(pattern => {
|
|
2488
|
+
let match;
|
|
2489
|
+
while ((match = pattern.exec(html)) !== null) {
|
|
2490
|
+
urls.push({ url: match[1], source: 'jwplayer_setup' });
|
|
2491
|
+
}
|
|
2492
|
+
});
|
|
2493
|
+
// ============================================================
|
|
2494
|
+
// 3. VIDEO.JS DETECTION
|
|
2495
|
+
// ============================================================
|
|
2496
|
+
const videoJsPlayers = document.querySelectorAll('.video-js, [data-setup], video[id^="vjs"]');
|
|
2497
|
+
videoJsPlayers.forEach(player => {
|
|
2498
|
+
const video = player.querySelector('source') || player;
|
|
2499
|
+
const src = video.getAttribute('src') || player.src;
|
|
2500
|
+
if (src)
|
|
2501
|
+
urls.push({ url: src, source: 'videojs' });
|
|
2502
|
+
});
|
|
2503
|
+
// ============================================================
|
|
2504
|
+
// 4. HLS.JS DETECTION
|
|
2505
|
+
// ============================================================
|
|
2506
|
+
const hlsPatterns = [
|
|
2507
|
+
/hls\.loadSource\(["']([^"']+)["']\)/gi,
|
|
2508
|
+
/Hls\.loadSource\(["']([^"']+)["']\)/gi,
|
|
2509
|
+
/source:\s*["']([^"']+\.m3u8[^"']*)["']/gi,
|
|
2510
|
+
/src:\s*["']([^"']+\.m3u8[^"']*)["']/gi,
|
|
2511
|
+
];
|
|
2512
|
+
hlsPatterns.forEach(pattern => {
|
|
2513
|
+
let match;
|
|
2514
|
+
while ((match = pattern.exec(html)) !== null) {
|
|
2515
|
+
urls.push({ url: match[1], source: 'hlsjs' });
|
|
2427
2516
|
}
|
|
2428
2517
|
});
|
|
2429
|
-
//
|
|
2518
|
+
// ============================================================
|
|
2519
|
+
// 5. PLYR DETECTION
|
|
2520
|
+
// ============================================================
|
|
2521
|
+
if (window.Plyr) {
|
|
2522
|
+
try {
|
|
2523
|
+
const plyrPlayer = window.player;
|
|
2524
|
+
if (plyrPlayer && plyrPlayer.source) {
|
|
2525
|
+
urls.push({ url: plyrPlayer.source, source: 'plyr' });
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
catch { /* ignore */ }
|
|
2529
|
+
}
|
|
2530
|
+
// ============================================================
|
|
2531
|
+
// 6. DATA ATTRIBUTES
|
|
2532
|
+
// ============================================================
|
|
2533
|
+
document.querySelectorAll('[data-src], [data-video], [data-file], [data-stream]').forEach(el => {
|
|
2534
|
+
const attrs = ['data-src', 'data-video', 'data-file', 'data-stream', 'data-link'];
|
|
2535
|
+
attrs.forEach(attr => {
|
|
2536
|
+
const val = el.getAttribute(attr);
|
|
2537
|
+
if (val && fmts.some(f => val.includes(`.${f}`))) {
|
|
2538
|
+
urls.push({ url: val, source: 'data_attr' });
|
|
2539
|
+
}
|
|
2540
|
+
});
|
|
2541
|
+
});
|
|
2542
|
+
// ============================================================
|
|
2543
|
+
// 7. STANDARD VIDEO/AUDIO ELEMENTS
|
|
2544
|
+
// ============================================================
|
|
2545
|
+
document.querySelectorAll('video, audio, source').forEach(el => {
|
|
2546
|
+
const src = el.getAttribute('src');
|
|
2547
|
+
if (src)
|
|
2548
|
+
urls.push({ url: src, source: 'html_media' });
|
|
2549
|
+
});
|
|
2550
|
+
// ============================================================
|
|
2551
|
+
// 8. DIRECT LINKS
|
|
2552
|
+
// ============================================================
|
|
2430
2553
|
document.querySelectorAll('a[href]').forEach(el => {
|
|
2431
2554
|
const href = el.href;
|
|
2432
2555
|
if (href && fmts.some(f => href.includes(`.${f}`))) {
|
|
2433
|
-
urls.push(href);
|
|
2556
|
+
urls.push({ url: href, source: 'direct_link' });
|
|
2434
2557
|
}
|
|
2435
2558
|
});
|
|
2436
|
-
//
|
|
2559
|
+
// ============================================================
|
|
2560
|
+
// 9. IFRAME PLAYERS
|
|
2561
|
+
// ============================================================
|
|
2437
2562
|
document.querySelectorAll('iframe').forEach(iframe => {
|
|
2438
2563
|
const src = iframe.src;
|
|
2439
|
-
if (src && (src.includes('player') || src.includes('embed'))) {
|
|
2440
|
-
urls.push(`iframe:${src}
|
|
2564
|
+
if (src && (src.includes('player') || src.includes('embed') || src.includes('video'))) {
|
|
2565
|
+
urls.push({ url: `iframe:${src}`, source: 'iframe' });
|
|
2441
2566
|
}
|
|
2442
2567
|
});
|
|
2443
|
-
|
|
2568
|
+
// ============================================================
|
|
2569
|
+
// 10. REGEX SCAN OF ENTIRE HTML
|
|
2570
|
+
// ============================================================
|
|
2571
|
+
fmts.forEach(fmt => {
|
|
2572
|
+
const pattern = new RegExp(`https?://[^"'\\s<>]+\\.${fmt}[^"'\\s<>]*`, 'gi');
|
|
2573
|
+
const matches = html.match(pattern);
|
|
2574
|
+
if (matches)
|
|
2575
|
+
matches.forEach(url => urls.push({ url, source: 'regex_scan' }));
|
|
2576
|
+
});
|
|
2577
|
+
// Deduplicate
|
|
2578
|
+
const seen = new Set();
|
|
2579
|
+
return urls.filter(u => {
|
|
2580
|
+
if (seen.has(u.url))
|
|
2581
|
+
return false;
|
|
2582
|
+
seen.add(u.url);
|
|
2583
|
+
return true;
|
|
2584
|
+
});
|
|
2444
2585
|
}, formats);
|
|
2445
2586
|
// Process found URLs
|
|
2446
|
-
for (const
|
|
2447
|
-
const format = formats.find(f => url.includes(`.${f}`)) || 'unknown';
|
|
2587
|
+
for (const item of extractedData) {
|
|
2588
|
+
const format = formats.find(f => item.url.includes(`.${f}`)) || 'unknown';
|
|
2448
2589
|
directUrls.push({
|
|
2449
|
-
url,
|
|
2590
|
+
url: item.url,
|
|
2450
2591
|
format,
|
|
2451
2592
|
quality: args.quality || 'auto',
|
|
2593
|
+
source: item.source,
|
|
2452
2594
|
});
|
|
2453
2595
|
}
|
|
2454
2596
|
// Check network requests for media URLs
|
|
2455
2597
|
const networkUrls = await page.evaluate((fmts) => {
|
|
2456
|
-
// Check performance entries for loaded resources
|
|
2457
2598
|
const resources = performance.getEntriesByType('resource');
|
|
2458
2599
|
return resources
|
|
2459
2600
|
.filter(r => fmts.some(f => r.name.includes(`.${f}`)))
|
|
@@ -2462,14 +2603,14 @@ export async function handleStreamExtractor(page, args) {
|
|
|
2462
2603
|
for (const url of networkUrls) {
|
|
2463
2604
|
if (!directUrls.some(d => d.url === url)) {
|
|
2464
2605
|
const format = formats.find(f => url.includes(`.${f}`)) || 'unknown';
|
|
2465
|
-
directUrls.push({ url, format });
|
|
2606
|
+
directUrls.push({ url, format, source: 'network' });
|
|
2466
2607
|
}
|
|
2467
2608
|
}
|
|
2468
2609
|
return {
|
|
2469
2610
|
success: directUrls.length > 0,
|
|
2470
2611
|
directUrls,
|
|
2471
2612
|
message: directUrls.length > 0
|
|
2472
|
-
?
|
|
2613
|
+
? `🎬 Found ${directUrls.length} direct URL(s) from ${new Set(directUrls.map(d => d.source)).size} sources`
|
|
2473
2614
|
: 'No direct URLs found',
|
|
2474
2615
|
};
|
|
2475
2616
|
}
|
|
@@ -80,6 +80,40 @@ export async function handleFindSelector(args) {
|
|
|
80
80
|
// Ensure elementType has a fallback value
|
|
81
81
|
const elementType = args?.elementType || '*';
|
|
82
82
|
tracker.setProgress(10, '🔧 Preparing search strategies...');
|
|
83
|
+
// ============================================================
|
|
84
|
+
// FUZZY MATCHING: Levenshtein distance for typo tolerance
|
|
85
|
+
// ============================================================
|
|
86
|
+
const fuzzyMatch = (str1, str2, threshold = 0.7) => {
|
|
87
|
+
const s1 = str1.toLowerCase();
|
|
88
|
+
const s2 = str2.toLowerCase();
|
|
89
|
+
// Exact match
|
|
90
|
+
if (s1 === s2)
|
|
91
|
+
return { match: true, score: 1 };
|
|
92
|
+
// Contains match
|
|
93
|
+
if (s1.includes(s2) || s2.includes(s1))
|
|
94
|
+
return { match: true, score: 0.9 };
|
|
95
|
+
// Levenshtein distance
|
|
96
|
+
const len1 = s1.length;
|
|
97
|
+
const len2 = s2.length;
|
|
98
|
+
const matrix = [];
|
|
99
|
+
for (let i = 0; i <= len1; i++)
|
|
100
|
+
matrix[i] = [i];
|
|
101
|
+
for (let j = 0; j <= len2; j++)
|
|
102
|
+
matrix[0][j] = j;
|
|
103
|
+
for (let i = 1; i <= len1; i++) {
|
|
104
|
+
for (let j = 1; j <= len2; j++) {
|
|
105
|
+
const cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
|
|
106
|
+
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, // deletion
|
|
107
|
+
matrix[i][j - 1] + 1, // insertion
|
|
108
|
+
matrix[i - 1][j - 1] + cost // substitution
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const distance = matrix[len1][len2];
|
|
113
|
+
const maxLen = Math.max(len1, len2);
|
|
114
|
+
const score = 1 - (distance / maxLen);
|
|
115
|
+
return { match: score >= threshold, score };
|
|
116
|
+
};
|
|
83
117
|
// Helper: Search in Shadow DOM
|
|
84
118
|
const searchInShadowDOM = async (sel) => {
|
|
85
119
|
return await pageInstance.evaluate((selector) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser-mcp-server",
|
|
3
|
-
"version": "2.24.
|
|
3
|
+
"version": "2.24.4",
|
|
4
4
|
"description": "🦁 MCP server for Brave Real Browser - NPM Workspaces Monorepo with anti-detection features, SSE streaming, and LSP compatibility",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@modelcontextprotocol/sdk": "latest",
|
|
52
52
|
"@types/turndown": "latest",
|
|
53
|
-
"brave-real-browser": "^2.5.
|
|
53
|
+
"brave-real-browser": "^2.5.4",
|
|
54
54
|
"turndown": "latest",
|
|
55
55
|
"vscode-languageserver": "^9.0.1",
|
|
56
56
|
"vscode-languageserver-textdocument": "^1.0.12"
|