brave-real-browser-mcp-server 2.9.21 → 2.10.0

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.
@@ -0,0 +1,483 @@
1
+ // Advanced Video Extraction Handlers
2
+ // Ad-Protection Bypass, Obfuscation Detection, Hidden Video Source Extraction
3
+ // @ts-nocheck
4
+ import { getCurrentPage } from '../browser-manager.js';
5
+ import { validateWorkflow } from '../workflow-validation.js';
6
+ import { withErrorHandling, sleep } from '../system-utils.js';
7
+ /**
8
+ * Advanced Video Source Extractor - Bypass ad-protection and extract all video sources
9
+ */
10
+ export async function handleAdvancedVideoExtraction(args) {
11
+ return await withErrorHandling(async () => {
12
+ validateWorkflow('advanced_video_extraction', {
13
+ requireBrowser: true,
14
+ requirePage: true,
15
+ });
16
+ const page = getCurrentPage();
17
+ const waitTime = args.waitTime || 10000;
18
+ // Collect all video-related data
19
+ const videoData = {
20
+ directVideoUrls: [],
21
+ m3u8Streams: [],
22
+ mpdStreams: [],
23
+ iframeSources: [],
24
+ obfuscatedUrls: [],
25
+ redirectChains: [],
26
+ hostingPlatforms: [],
27
+ downloadLinks: [],
28
+ timestamp: new Date().toISOString()
29
+ };
30
+ // Monitor network for video content
31
+ const networkRequests = [];
32
+ const requestHandler = (request) => {
33
+ const url = request.url();
34
+ const resourceType = request.resourceType();
35
+ networkRequests.push({
36
+ url,
37
+ method: request.method(),
38
+ resourceType,
39
+ headers: request.headers()
40
+ });
41
+ // Detect video URLs
42
+ if (resourceType === 'media' ||
43
+ url.includes('.mp4') ||
44
+ url.includes('.m3u8') ||
45
+ url.includes('.mpd') ||
46
+ url.includes('.webm') ||
47
+ url.includes('video')) {
48
+ videoData.directVideoUrls.push({
49
+ url,
50
+ type: resourceType,
51
+ detected: 'network_monitor'
52
+ });
53
+ }
54
+ };
55
+ const responseHandler = async (response) => {
56
+ try {
57
+ const url = response.url();
58
+ const contentType = response.headers()['content-type'] || '';
59
+ // Check for video content
60
+ if (contentType.includes('video') || contentType.includes('application/vnd.apple.mpegurl')) {
61
+ videoData.directVideoUrls.push({
62
+ url,
63
+ contentType,
64
+ status: response.status(),
65
+ detected: 'response_monitor'
66
+ });
67
+ }
68
+ // Check for m3u8 playlists
69
+ if (url.includes('.m3u8')) {
70
+ videoData.m3u8Streams.push({
71
+ url,
72
+ status: response.status(),
73
+ type: 'HLS'
74
+ });
75
+ }
76
+ // Check for DASH manifests
77
+ if (url.includes('.mpd')) {
78
+ videoData.mpdStreams.push({
79
+ url,
80
+ status: response.status(),
81
+ type: 'DASH'
82
+ });
83
+ }
84
+ }
85
+ catch (e) {
86
+ // Ignore errors
87
+ }
88
+ };
89
+ page.on('request', requestHandler);
90
+ page.on('response', responseHandler);
91
+ // Extract page content
92
+ const pageAnalysis = await page.evaluate(() => {
93
+ const results = {
94
+ iframes: [],
95
+ videoElements: [],
96
+ obfuscatedScripts: [],
97
+ possibleHosts: [],
98
+ downloadButtons: []
99
+ };
100
+ // 1. Extract all iframes
101
+ document.querySelectorAll('iframe').forEach((iframe) => {
102
+ results.iframes.push({
103
+ src: iframe.src,
104
+ dataSrc: iframe.getAttribute('data-src'),
105
+ id: iframe.id,
106
+ className: iframe.className
107
+ });
108
+ });
109
+ // 2. Extract video elements
110
+ document.querySelectorAll('video').forEach((video) => {
111
+ const sources = [];
112
+ if (video.src)
113
+ sources.push({ src: video.src, type: 'direct' });
114
+ video.querySelectorAll('source').forEach((source) => {
115
+ sources.push({
116
+ src: source.src,
117
+ type: source.type,
118
+ quality: source.dataset.quality
119
+ });
120
+ });
121
+ results.videoElements.push({
122
+ sources,
123
+ poster: video.poster,
124
+ currentSrc: video.currentSrc
125
+ });
126
+ });
127
+ // 3. Detect obfuscated JavaScript
128
+ document.querySelectorAll('script').forEach((script) => {
129
+ const content = script.textContent || '';
130
+ // Check for common obfuscation patterns
131
+ if (content.includes('eval(') ||
132
+ content.includes('atob(') ||
133
+ content.includes('\\x') ||
134
+ content.match(/0x[0-9a-f]{4}/gi) ||
135
+ content.includes('_0x')) {
136
+ // Try to extract URLs from obfuscated code
137
+ const urlPatterns = [
138
+ /https?:\/\/[^\s"']+\.m3u8[^\s"']*/gi,
139
+ /https?:\/\/[^\s"']+\.mp4[^\s"']*/gi,
140
+ /https?:\/\/[^\s"']+\.mpd[^\s"']*/gi,
141
+ /https?:\/\/[^\s"']+video[^\s"']*/gi
142
+ ];
143
+ const foundUrls = [];
144
+ urlPatterns.forEach(pattern => {
145
+ const matches = content.match(pattern);
146
+ if (matches)
147
+ foundUrls.push(...matches);
148
+ });
149
+ results.obfuscatedScripts.push({
150
+ hasObfuscation: true,
151
+ patterns: {
152
+ hasEval: content.includes('eval('),
153
+ hasAtob: content.includes('atob('),
154
+ hasHexEncoding: content.includes('\\x'),
155
+ hasObfuscatedVars: content.includes('_0x')
156
+ },
157
+ extractedUrls: foundUrls,
158
+ snippet: content.substring(0, 200)
159
+ });
160
+ }
161
+ // Extract video hosting domains
162
+ const hostPatterns = [
163
+ /streamtape/gi,
164
+ /doodstream/gi,
165
+ /filemoon/gi,
166
+ /streamwish/gi,
167
+ /mixdrop/gi,
168
+ /upstream/gi,
169
+ /voe\.sx/gi,
170
+ /streamlare/gi,
171
+ /upns\.online/gi
172
+ ];
173
+ hostPatterns.forEach(pattern => {
174
+ if (pattern.test(content)) {
175
+ results.possibleHosts.push(pattern.source);
176
+ }
177
+ });
178
+ });
179
+ // 4. Find download buttons and links
180
+ const downloadSelectors = [
181
+ 'a[download]',
182
+ 'a[href*="download"]',
183
+ 'button[data-download]',
184
+ 'a[href*=".mp4"]',
185
+ 'a[href*=".mkv"]',
186
+ 'a[class*="download"]'
187
+ ];
188
+ downloadSelectors.forEach(selector => {
189
+ document.querySelectorAll(selector).forEach((el) => {
190
+ results.downloadButtons.push({
191
+ href: el.href || el.getAttribute('href'),
192
+ text: el.textContent?.trim(),
193
+ selector
194
+ });
195
+ });
196
+ });
197
+ return results;
198
+ });
199
+ // Merge page analysis into videoData
200
+ videoData.iframeSources = pageAnalysis.iframes;
201
+ videoData.obfuscatedUrls = pageAnalysis.obfuscatedScripts;
202
+ videoData.hostingPlatforms = [...new Set(pageAnalysis.possibleHosts)];
203
+ videoData.downloadLinks = pageAnalysis.downloadButtons;
204
+ // Wait for dynamic content
205
+ await sleep(waitTime);
206
+ // Try to click play buttons to trigger video loading
207
+ try {
208
+ const playButton = await page.$('button[class*="play"], .play-button, [aria-label*="Play"]');
209
+ if (playButton) {
210
+ await playButton.click();
211
+ await sleep(3000);
212
+ }
213
+ }
214
+ catch (e) {
215
+ // Play button not found or not clickable
216
+ }
217
+ page.off('request', requestHandler);
218
+ page.off('response', responseHandler);
219
+ // Deduplicate URLs
220
+ videoData.directVideoUrls = [...new Map(videoData.directVideoUrls.map((item) => [item.url, item])).values()];
221
+ videoData.m3u8Streams = [...new Map(videoData.m3u8Streams.map((item) => [item.url, item])).values()];
222
+ // Create summary
223
+ const summary = `
224
+ 🎬 Advanced Video Extraction Results
225
+ ════════════════════════════════════
226
+
227
+ 📊 Summary:
228
+ • Direct Video URLs: ${videoData.directVideoUrls.length}
229
+ • HLS Streams (m3u8): ${videoData.m3u8Streams.length}
230
+ • DASH Streams (mpd): ${videoData.mpdStreams.length}
231
+ • IFrame Sources: ${videoData.iframeSources.length}
232
+ • Obfuscated Scripts: ${videoData.obfuscatedUrls.length}
233
+ • Download Links: ${videoData.downloadLinks.length}
234
+ • Detected Platforms: ${videoData.hostingPlatforms.length}
235
+
236
+ ${videoData.directVideoUrls.length > 0 ? `
237
+ 🎥 Direct Video URLs:
238
+ ${videoData.directVideoUrls.map((v, i) => ` ${i + 1}. ${v.url}\n Type: ${v.type || 'unknown'}\n Detected: ${v.detected}`).join('\n')}
239
+ ` : ''}
240
+
241
+ ${videoData.m3u8Streams.length > 0 ? `
242
+ 📺 HLS Streams:
243
+ ${videoData.m3u8Streams.map((s, i) => ` ${i + 1}. ${s.url}`).join('\n')}
244
+ ` : ''}
245
+
246
+ ${videoData.iframeSources.length > 0 ? `
247
+ 🔗 IFrame Sources:
248
+ ${videoData.iframeSources.map((f, i) => ` ${i + 1}. ${f.src || f.dataSrc}`).join('\n')}
249
+ ` : ''}
250
+
251
+ ${videoData.hostingPlatforms.length > 0 ? `
252
+ 🌐 Detected Hosting Platforms:
253
+ ${videoData.hostingPlatforms.map((h, i) => ` ${i + 1}. ${h}`).join('\n')}
254
+ ` : ''}
255
+
256
+ ${videoData.obfuscatedUrls.length > 0 ? `
257
+ 🔐 Obfuscated Content Detected:
258
+ • Scripts with obfuscation: ${videoData.obfuscatedUrls.length}
259
+ • URLs extracted from obfuscated code: ${videoData.obfuscatedUrls.reduce((acc, s) => acc + (s.extractedUrls?.length || 0), 0)}
260
+ ` : ''}
261
+
262
+ ${videoData.downloadLinks.length > 0 ? `
263
+ ⬇️ Download Links:
264
+ ${videoData.downloadLinks.slice(0, 5).map((d, i) => ` ${i + 1}. ${d.text}: ${d.href}`).join('\n')}
265
+ ${videoData.downloadLinks.length > 5 ? ` ... and ${videoData.downloadLinks.length - 5} more` : ''}
266
+ ` : ''}
267
+
268
+ 📋 Full Data (JSON):
269
+ ${JSON.stringify(videoData, null, 2)}
270
+ `;
271
+ return {
272
+ content: [{
273
+ type: 'text',
274
+ text: summary
275
+ }]
276
+ };
277
+ }, 'Failed to extract advanced video sources');
278
+ }
279
+ /**
280
+ * Deobfuscate JavaScript - Attempt to decode obfuscated JavaScript
281
+ */
282
+ export async function handleDeobfuscateJS(args) {
283
+ return await withErrorHandling(async () => {
284
+ validateWorkflow('deobfuscate_js', {
285
+ requireBrowser: true,
286
+ requirePage: true,
287
+ });
288
+ const page = getCurrentPage();
289
+ const deobfuscationResults = await page.evaluate(() => {
290
+ const results = [];
291
+ document.querySelectorAll('script').forEach((script, index) => {
292
+ const content = script.textContent || '';
293
+ if (content.length < 100)
294
+ return;
295
+ const analysis = {
296
+ scriptIndex: index,
297
+ obfuscationType: [],
298
+ extractedData: {
299
+ urls: [],
300
+ domains: [],
301
+ apiKeys: [],
302
+ base64Strings: []
303
+ }
304
+ };
305
+ // Detect obfuscation types
306
+ if (content.includes('eval('))
307
+ analysis.obfuscationType.push('eval');
308
+ if (content.includes('atob('))
309
+ analysis.obfuscationType.push('base64');
310
+ if (content.match(/0x[0-9a-f]{4}/gi))
311
+ analysis.obfuscationType.push('hex');
312
+ if (content.match(/_0x[0-9a-f]+/gi))
313
+ analysis.obfuscationType.push('identifier_obfuscation');
314
+ if (content.includes('\\x'))
315
+ analysis.obfuscationType.push('hex_escape');
316
+ if (analysis.obfuscationType.length === 0)
317
+ return;
318
+ // Extract URLs
319
+ const urlPattern = /https?:\/\/[^\s"'<>]+/gi;
320
+ const urls = content.match(urlPattern);
321
+ if (urls) {
322
+ analysis.extractedData.urls = [...new Set(urls)];
323
+ }
324
+ // Extract base64 encoded strings
325
+ const base64Pattern = /["']([A-Za-z0-9+/]{20,}={0,2})["']/g;
326
+ let match;
327
+ while ((match = base64Pattern.exec(content)) !== null) {
328
+ try {
329
+ const decoded = atob(match[1]);
330
+ if (decoded.includes('http') || decoded.includes('video') || decoded.includes('.m3u8')) {
331
+ analysis.extractedData.base64Strings.push({
332
+ original: match[1].substring(0, 50) + '...',
333
+ decoded: decoded.substring(0, 200)
334
+ });
335
+ }
336
+ }
337
+ catch (e) {
338
+ // Not valid base64
339
+ }
340
+ }
341
+ // Extract potential domains
342
+ const domainPattern = /[a-z0-9][a-z0-9-]*\.(com|net|org|io|tv|online|xyz|cc)/gi;
343
+ const domains = content.match(domainPattern);
344
+ if (domains) {
345
+ analysis.extractedData.domains = [...new Set(domains)];
346
+ }
347
+ results.push(analysis);
348
+ });
349
+ return results.filter(r => r.obfuscationType.length > 0);
350
+ });
351
+ return {
352
+ content: [{
353
+ type: 'text',
354
+ text: `🔓 Deobfuscation Results:\n\nFound ${deobfuscationResults.length} obfuscated scripts\n\n${JSON.stringify(deobfuscationResults, null, 2)}`
355
+ }]
356
+ };
357
+ }, 'Failed to deobfuscate JavaScript');
358
+ }
359
+ /**
360
+ * Multi-Layer Redirect Tracer - Follow multiple redirect layers to find final video source
361
+ */
362
+ export async function handleMultiLayerRedirectTrace(args) {
363
+ return await withErrorHandling(async () => {
364
+ validateWorkflow('multi_layer_redirect_trace', {
365
+ requireBrowser: true,
366
+ requirePage: true,
367
+ });
368
+ const page = getCurrentPage();
369
+ const startUrl = args.url;
370
+ const maxDepth = args.maxDepth || 5;
371
+ if (!startUrl) {
372
+ throw new Error('URL is required');
373
+ }
374
+ const redirectChain = [];
375
+ let currentDepth = 0;
376
+ let currentUrl = startUrl;
377
+ while (currentDepth < maxDepth) {
378
+ try {
379
+ const allRequests = [];
380
+ const responseHandler = (response) => {
381
+ allRequests.push({
382
+ url: response.url(),
383
+ status: response.status(),
384
+ redirected: response.request().redirectChain().length > 0,
385
+ finalUrl: response.url()
386
+ });
387
+ };
388
+ page.on('response', responseHandler);
389
+ await page.goto(currentUrl, {
390
+ waitUntil: 'networkidle0',
391
+ timeout: 30000
392
+ });
393
+ await sleep(2000);
394
+ page.off('response', responseHandler);
395
+ const finalUrl = page.url();
396
+ // Check for iframe redirects
397
+ const iframes = await page.evaluate(() => {
398
+ return Array.from(document.querySelectorAll('iframe')).map((f) => f.src);
399
+ });
400
+ redirectChain.push({
401
+ depth: currentDepth,
402
+ startUrl: currentUrl,
403
+ finalUrl,
404
+ iframes,
405
+ requests: allRequests.length
406
+ });
407
+ // If we found an iframe, follow it
408
+ if (iframes.length > 0 && iframes[0] !== currentUrl) {
409
+ currentUrl = iframes[0];
410
+ currentDepth++;
411
+ }
412
+ else {
413
+ break;
414
+ }
415
+ }
416
+ catch (e) {
417
+ redirectChain.push({
418
+ depth: currentDepth,
419
+ error: e.message
420
+ });
421
+ break;
422
+ }
423
+ }
424
+ return {
425
+ content: [{
426
+ type: 'text',
427
+ text: `🔄 Multi-Layer Redirect Trace:\n\nTotal Layers: ${redirectChain.length}\nMax Depth Reached: ${currentDepth >= maxDepth}\n\n${JSON.stringify(redirectChain, null, 2)}`
428
+ }]
429
+ };
430
+ }, 'Failed to trace multi-layer redirects');
431
+ }
432
+ /**
433
+ * Ad Blocker Detector - Detect and report ad-protection mechanisms
434
+ */
435
+ export async function handleAdProtectionDetector(args) {
436
+ return await withErrorHandling(async () => {
437
+ validateWorkflow('ad_protection_detector', {
438
+ requireBrowser: true,
439
+ requirePage: true,
440
+ });
441
+ const page = getCurrentPage();
442
+ const adProtection = await page.evaluate(() => {
443
+ const results = {
444
+ adBlockDetection: false,
445
+ antiDebugger: false,
446
+ obfuscatedCode: false,
447
+ popupLayers: 0,
448
+ hiddenElements: 0,
449
+ suspiciousScripts: []
450
+ };
451
+ // Check for common ad-block detection
452
+ const adBlockIndicators = [
453
+ 'adblock',
454
+ 'ublock',
455
+ 'adguard',
456
+ 'please disable',
457
+ 'turn off ad blocker'
458
+ ];
459
+ const bodyText = document.body.textContent?.toLowerCase() || '';
460
+ results.adBlockDetection = adBlockIndicators.some(indicator => bodyText.includes(indicator));
461
+ // Check for anti-debugger code
462
+ document.querySelectorAll('script').forEach((script) => {
463
+ const content = script.textContent || '';
464
+ if (content.includes('debugger') || content.includes('devtools')) {
465
+ results.antiDebugger = true;
466
+ }
467
+ });
468
+ // Count popup layers
469
+ const overlays = document.querySelectorAll('[style*="position: fixed"], [style*="z-index"]');
470
+ results.popupLayers = overlays.length;
471
+ // Check for hidden elements
472
+ const hidden = document.querySelectorAll('[style*="display: none"], [style*="visibility: hidden"]');
473
+ results.hiddenElements = hidden.length;
474
+ return results;
475
+ });
476
+ return {
477
+ content: [{
478
+ type: 'text',
479
+ text: `🛡️ Ad Protection Analysis:\n\n${JSON.stringify(adProtection, null, 2)}`
480
+ }]
481
+ };
482
+ }, 'Failed to detect ad protection');
483
+ }
@@ -3,7 +3,7 @@
3
3
  // @ts-nocheck
4
4
  import { getCurrentPage } from '../browser-manager.js';
5
5
  import { validateWorkflow } from '../workflow-validation.js';
6
- import { withErrorHandling } from '../system-utils.js';
6
+ import { withErrorHandling, sleep } from '../system-utils.js';
7
7
  /**
8
8
  * Video Link Finder - Find all video links on page
9
9
  */
@@ -184,7 +184,7 @@ export async function handleVideoDownloadButton(args) {
184
184
  const selector = customSelector || 'a[download], button[download]';
185
185
  try {
186
186
  await page.click(selector);
187
- await page.waitForTimeout(2000);
187
+ await sleep(2000);
188
188
  return {
189
189
  content: [{
190
190
  type: 'text',
@@ -252,7 +252,7 @@ export async function handleVideoPlayPushSource(args) {
252
252
  }
253
253
  }
254
254
  // Wait for sources to load
255
- await page.waitForTimeout(3000);
255
+ await sleep(3000);
256
256
  page.off('response', responseHandler);
257
257
  return {
258
258
  content: [{
@@ -370,70 +370,110 @@ export async function handleUrlRedirectTraceEndpoints(args) {
370
370
  * Network Recording Finder - Find and analyze network recordings
371
371
  */
372
372
  export async function handleNetworkRecordingFinder(args) {
373
- return await withErrorHandling(async () => {
374
- validateWorkflow('network_recording_finder', {
373
+ try {
374
+ const validation = validateWorkflow('network_recording_finder', {
375
375
  requireBrowser: true,
376
376
  requirePage: true,
377
377
  });
378
+ if (!validation.isValid) {
379
+ return {
380
+ content: [{
381
+ type: 'text',
382
+ text: `⚠️ ${validation.errorMessage || 'Workflow validation failed'}`,
383
+ }],
384
+ isError: true,
385
+ };
386
+ }
378
387
  const page = getCurrentPage();
379
388
  const duration = args.duration || 10000;
380
389
  const filterType = args.filterType || 'video'; // video, audio, media
381
390
  const recordings = [];
382
391
  const responseHandler = async (response) => {
383
- const url = response.url();
384
- const contentType = response.headers()['content-type'] || '';
385
- const resourceType = response.request().resourceType();
386
- let shouldRecord = false;
387
- if (filterType === 'video' && (contentType.includes('video') || resourceType === 'media')) {
388
- shouldRecord = true;
389
- }
390
- else if (filterType === 'audio' && contentType.includes('audio')) {
391
- shouldRecord = true;
392
- }
393
- else if (filterType === 'media' && (contentType.includes('video') || contentType.includes('audio'))) {
394
- shouldRecord = true;
395
- }
396
- if (shouldRecord) {
397
- try {
398
- const buffer = await response.buffer();
399
- recordings.push({
400
- url,
401
- contentType,
402
- size: buffer.length,
403
- status: response.status(),
404
- timestamp: new Date().toISOString(),
405
- });
392
+ try {
393
+ const url = response.url();
394
+ const contentType = response.headers()['content-type'] || '';
395
+ const resourceType = response.request().resourceType();
396
+ let shouldRecord = false;
397
+ if (filterType === 'video' && (contentType.includes('video') || resourceType === 'media')) {
398
+ shouldRecord = true;
406
399
  }
407
- catch (e) {
408
- recordings.push({
409
- url,
410
- contentType,
411
- status: response.status(),
412
- error: 'Could not capture buffer',
413
- });
400
+ else if (filterType === 'audio' && contentType.includes('audio')) {
401
+ shouldRecord = true;
414
402
  }
403
+ else if (filterType === 'media' && (contentType.includes('video') || contentType.includes('audio'))) {
404
+ shouldRecord = true;
405
+ }
406
+ if (shouldRecord) {
407
+ try {
408
+ const buffer = await response.buffer();
409
+ recordings.push({
410
+ url,
411
+ contentType,
412
+ size: buffer.length,
413
+ status: response.status(),
414
+ timestamp: new Date().toISOString(),
415
+ });
416
+ }
417
+ catch (e) {
418
+ recordings.push({
419
+ url,
420
+ contentType,
421
+ status: response.status(),
422
+ error: 'Could not capture buffer',
423
+ });
424
+ }
425
+ }
426
+ }
427
+ catch (e) {
428
+ // Ignore individual response errors
415
429
  }
416
430
  };
417
431
  page.on('response', responseHandler);
418
- await page.waitForTimeout(duration);
432
+ await sleep(duration);
419
433
  page.off('response', responseHandler);
434
+ if (recordings.length === 0) {
435
+ return {
436
+ content: [{
437
+ type: 'text',
438
+ text: `ℹ️ No ${filterType} recordings found during ${duration}ms monitoring period.\n\n💡 Note: Network monitoring starts AFTER this tool is called. To capture video/media requests:\n 1. Start monitoring before navigating to the page\n 2. Or trigger video playback after monitoring starts\n 3. Consider using 'advanced_video_extraction' for comprehensive detection`,
439
+ }],
440
+ };
441
+ }
420
442
  return {
421
443
  content: [{
422
444
  type: 'text',
423
445
  text: `✅ Network Recordings Found: ${recordings.length}\n\n${JSON.stringify(recordings, null, 2)}`,
424
446
  }],
425
447
  };
426
- }, 'Failed to find network recordings');
448
+ }
449
+ catch (error) {
450
+ return {
451
+ content: [{
452
+ type: 'text',
453
+ text: `❌ Network recording finder failed: ${error instanceof Error ? error.message : String(error)}`,
454
+ }],
455
+ isError: true,
456
+ };
457
+ }
427
458
  }
428
459
  /**
429
460
  * Network Recording Extractors - Extract data from network recordings
430
461
  */
431
462
  export async function handleNetworkRecordingExtractors(args) {
432
- return await withErrorHandling(async () => {
433
- validateWorkflow('network_recording_extractors', {
463
+ try {
464
+ const validation = validateWorkflow('network_recording_extractors', {
434
465
  requireBrowser: true,
435
466
  requirePage: true,
436
467
  });
468
+ if (!validation.isValid) {
469
+ return {
470
+ content: [{
471
+ type: 'text',
472
+ text: `⚠️ ${validation.errorMessage || 'Workflow validation failed'}`,
473
+ }],
474
+ isError: true,
475
+ };
476
+ }
437
477
  const page = getCurrentPage();
438
478
  const duration = args.duration || 10000;
439
479
  const extractedData = {
@@ -484,15 +524,34 @@ export async function handleNetworkRecordingExtractors(args) {
484
524
  }
485
525
  };
486
526
  page.on('response', responseHandler);
487
- await page.waitForTimeout(duration);
527
+ await sleep(duration);
488
528
  page.off('response', responseHandler);
529
+ const totalFound = extractedData.videos.length + extractedData.audio.length +
530
+ extractedData.manifests.length + extractedData.apis.length;
531
+ if (totalFound === 0) {
532
+ return {
533
+ content: [{
534
+ type: 'text',
535
+ text: `ℹ️ No media content extracted during ${duration}ms monitoring.\n\n💡 Suggestions:\n • Network monitoring captures requests made AFTER the tool starts\n • Try starting monitoring before page navigation\n • Use 'advanced_video_extraction' for analyzing already-loaded content\n • Consider longer duration if content loads slowly`,
536
+ }],
537
+ };
538
+ }
489
539
  return {
490
540
  content: [{
491
541
  type: 'text',
492
542
  text: `✅ Network Recording Extraction Complete\n\nVideos: ${extractedData.videos.length}\nAudio: ${extractedData.audio.length}\nManifests: ${extractedData.manifests.length}\nAPIs: ${extractedData.apis.length}\n\n${JSON.stringify(extractedData, null, 2)}`,
493
543
  }],
494
544
  };
495
- }, 'Failed to extract network recordings');
545
+ }
546
+ catch (error) {
547
+ return {
548
+ content: [{
549
+ type: 'text',
550
+ text: `❌ Network recording extraction failed: ${error instanceof Error ? error.message : String(error)}`,
551
+ }],
552
+ isError: true,
553
+ };
554
+ }
496
555
  }
497
556
  /**
498
557
  * Video Links Finders - Advanced video link detection