brave-real-browser-mcp-server 2.9.8 → 2.9.10

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.
@@ -907,3 +907,10 @@ export function getContentPriorityConfig() {
907
907
  export function updateContentPriorityConfig(config) {
908
908
  contentPriorityConfig = { ...contentPriorityConfig, ...config };
909
909
  }
910
+ // Additional convenience exports for video extraction modules
911
+ export function getBrowser() {
912
+ return browserInstance;
913
+ }
914
+ export function getPage() {
915
+ return pageInstance;
916
+ }
@@ -49,7 +49,7 @@ export async function handleBrowserClose() {
49
49
  });
50
50
  }
51
51
  // Workflow validation wrapper
52
- async function withWorkflowValidation(toolName, args, operation) {
52
+ export async function withWorkflowValidation(toolName, args, operation) {
53
53
  // Validate workflow state before execution
54
54
  const validation = validateWorkflow(toolName, args);
55
55
  // Defensive check: if validation is undefined or null, allow execution (test environment)
@@ -0,0 +1,464 @@
1
+ // Specialized Tools Handlers for Video Extraction, Links Finding, AJAX, and User Agent Tools
2
+ import { getBrowser, getPage } from '../browser-manager.js';
3
+ import { withWorkflowValidation } from './browser-handlers.js';
4
+ import { withErrorHandling } from '../system-utils.js';
5
+ // Links Finders Handler
6
+ export async function handleLinksFinders(args) {
7
+ return withWorkflowValidation('links_finders', args, async () => {
8
+ return withErrorHandling(async () => {
9
+ const browser = getBrowser();
10
+ const page = getPage();
11
+ if (!browser || !page) {
12
+ throw new Error('Browser not initialized. Call browser_init first.');
13
+ }
14
+ const { includeExternal = true, includeInternal = true, includeMediaLinks = true, includeEmailPhone = false, filterDomains = [] } = args;
15
+ const links = await page.evaluate((options) => {
16
+ const { includeExternal, includeInternal, includeMediaLinks, includeEmailPhone, filterDomains } = options;
17
+ const results = [];
18
+ const currentDomain = window.location.hostname;
19
+ // Find all anchor links
20
+ const anchors = document.querySelectorAll('a[href]');
21
+ anchors.forEach((anchor) => {
22
+ const href = anchor.href;
23
+ const isExternal = !href.includes(currentDomain);
24
+ if ((isExternal && includeExternal) || (!isExternal && includeInternal)) {
25
+ if (filterDomains.length === 0 || filterDomains.some((domain) => href.includes(domain))) {
26
+ results.push({
27
+ type: 'anchor',
28
+ url: href,
29
+ text: anchor.textContent?.trim() || '',
30
+ isExternal,
31
+ title: anchor.title || ''
32
+ });
33
+ }
34
+ }
35
+ });
36
+ // Find media links if requested
37
+ if (includeMediaLinks) {
38
+ const mediaElements = document.querySelectorAll('img[src], video[src], audio[src], source[src]');
39
+ mediaElements.forEach((element) => {
40
+ results.push({
41
+ type: 'media',
42
+ url: element.src,
43
+ mediaType: element.tagName.toLowerCase(),
44
+ alt: element.alt || '',
45
+ title: element.title || ''
46
+ });
47
+ });
48
+ }
49
+ // Find email and phone links if requested
50
+ if (includeEmailPhone) {
51
+ const emailPhoneLinks = document.querySelectorAll('a[href^="mailto:"], a[href^="tel:"]');
52
+ emailPhoneLinks.forEach((link) => {
53
+ results.push({
54
+ type: link.href.startsWith('mailto:') ? 'email' : 'phone',
55
+ url: link.href,
56
+ text: link.textContent?.trim() || ''
57
+ });
58
+ });
59
+ }
60
+ return results;
61
+ }, { includeExternal, includeInternal, includeMediaLinks, includeEmailPhone, filterDomains });
62
+ return {
63
+ success: true,
64
+ links,
65
+ total: links.length,
66
+ message: `Found ${links.length} links on the page`
67
+ };
68
+ }, 'Failed to find links');
69
+ });
70
+ }
71
+ // Video Play Sources Finder Handler
72
+ export async function handleVideoPlaySourcesFinder(args) {
73
+ return withWorkflowValidation('video_play_sources_finder', args, async () => {
74
+ return withErrorHandling(async () => {
75
+ const browser = getBrowser();
76
+ const page = getPage();
77
+ if (!browser || !page) {
78
+ throw new Error('Browser not initialized. Call browser_init first.');
79
+ }
80
+ const { includeHLS = true, includeDASH = true, includeDirectMP4 = true, includeBlob = true, recordingDuration = 15000 } = args;
81
+ // Start network monitoring
82
+ const networkSources = [];
83
+ await page.setRequestInterception(true);
84
+ page.on('request', (request) => {
85
+ request.continue();
86
+ });
87
+ page.on('response', async (response) => {
88
+ try {
89
+ const url = response.url();
90
+ const contentType = response.headers()['content-type'] || '';
91
+ // Check for video sources
92
+ const isVideoSource = ((includeHLS && (url.includes('.m3u8') || contentType.includes('application/x-mpegURL'))) ||
93
+ (includeDASH && (url.includes('.mpd') || contentType.includes('application/dash+xml'))) ||
94
+ (includeDirectMP4 && (url.includes('.mp4') || contentType.includes('video/mp4'))) ||
95
+ (includeBlob && url.startsWith('blob:')));
96
+ if (isVideoSource) {
97
+ networkSources.push({
98
+ url,
99
+ type: url.includes('.m3u8') ? 'HLS' :
100
+ url.includes('.mpd') ? 'DASH' :
101
+ url.includes('.mp4') ? 'MP4' :
102
+ url.startsWith('blob:') ? 'BLOB' : 'UNKNOWN',
103
+ contentType,
104
+ size: response.headers()['content-length'] || 'unknown',
105
+ status: response.status()
106
+ });
107
+ }
108
+ }
109
+ catch (error) {
110
+ // Ignore response processing errors
111
+ }
112
+ });
113
+ // Wait for network activity
114
+ await new Promise(resolve => setTimeout(resolve, recordingDuration));
115
+ await page.setRequestInterception(false);
116
+ // Also search DOM for video sources
117
+ const domSources = await page.evaluate((options) => {
118
+ const { includeHLS, includeDASH, includeDirectMP4, includeBlob } = options;
119
+ const sources = [];
120
+ // Video elements
121
+ const videos = document.querySelectorAll('video');
122
+ videos.forEach((video) => {
123
+ if (video.src)
124
+ sources.push({ url: video.src, type: 'VIDEO_TAG', source: 'dom' });
125
+ const sourceTags = video.querySelectorAll('source');
126
+ sourceTags.forEach((source) => {
127
+ if (source.src)
128
+ sources.push({ url: source.src, type: source.type || 'VIDEO_SOURCE', source: 'dom' });
129
+ });
130
+ });
131
+ // Search for sources in page text/scripts
132
+ const scripts = document.querySelectorAll('script');
133
+ scripts.forEach((script) => {
134
+ const content = script.textContent || '';
135
+ if (includeHLS) {
136
+ const hlsMatches = content.match(/https?:\/\/[^\s"']+\.m3u8[^\s"']*/g);
137
+ if (hlsMatches) {
138
+ hlsMatches.forEach(url => sources.push({ url, type: 'HLS', source: 'script' }));
139
+ }
140
+ }
141
+ if (includeDASH) {
142
+ const dashMatches = content.match(/https?:\/\/[^\s"']+\.mpd[^\s"']*/g);
143
+ if (dashMatches) {
144
+ dashMatches.forEach(url => sources.push({ url, type: 'DASH', source: 'script' }));
145
+ }
146
+ }
147
+ if (includeDirectMP4) {
148
+ const mp4Matches = content.match(/https?:\/\/[^\s"']+\.mp4[^\s"']*/g);
149
+ if (mp4Matches) {
150
+ mp4Matches.forEach(url => sources.push({ url, type: 'MP4', source: 'script' }));
151
+ }
152
+ }
153
+ });
154
+ return sources;
155
+ }, { includeHLS, includeDASH, includeDirectMP4, includeBlob });
156
+ const allSources = [...networkSources, ...domSources];
157
+ const uniqueSources = allSources.filter((source, index, array) => array.findIndex(s => s.url === source.url) === index);
158
+ return {
159
+ success: true,
160
+ sources: uniqueSources,
161
+ networkSources: networkSources.length,
162
+ domSources: domSources.length,
163
+ total: uniqueSources.length,
164
+ message: `Found ${uniqueSources.length} video play sources`
165
+ };
166
+ }, 'Failed to find video play sources');
167
+ });
168
+ }
169
+ // Video Player Hostars Sources Finder Handler
170
+ export async function handleVideoPlayerHostarsSourcesFinder(args) {
171
+ return withWorkflowValidation('video_player_hostars_sources_finder', args, async () => {
172
+ return withErrorHandling(async () => {
173
+ const browser = getBrowser();
174
+ const page = getPage();
175
+ if (!browser || !page) {
176
+ throw new Error('Browser not initialized. Call browser_init first.');
177
+ }
178
+ const { platforms = ['all'], extractEmbedCodes = true, extractAPIKeys = false } = args;
179
+ const hostingSources = await page.evaluate((options) => {
180
+ const { platforms, extractEmbedCodes, extractAPIKeys } = options;
181
+ const sources = [];
182
+ // Define hosting platform patterns
183
+ const hostingPatterns = {
184
+ youtube: [
185
+ /(?:youtube\.com|youtu\.be)/i,
186
+ /\/embed\/([a-zA-Z0-9_-]+)/,
187
+ /watch\?v=([a-zA-Z0-9_-]+)/
188
+ ],
189
+ vimeo: [
190
+ /vimeo\.com/i,
191
+ /\/video\/(\d+)/,
192
+ /player\.vimeo\.com\/video\/(\d+)/
193
+ ],
194
+ dailymotion: [
195
+ /dailymotion\.com/i,
196
+ /\/video\/([a-zA-Z0-9_-]+)/
197
+ ],
198
+ twitch: [
199
+ /twitch\.tv/i,
200
+ /\/videos\/(\d+)/,
201
+ /\/embed\/([a-zA-Z0-9_-]+)/
202
+ ],
203
+ facebook: [
204
+ /facebook\.com/i,
205
+ /\/watch/,
206
+ /\/videos/
207
+ ],
208
+ instagram: [
209
+ /instagram\.com/i,
210
+ /\/p\/([a-zA-Z0-9_-]+)/
211
+ ],
212
+ tiktok: [
213
+ /tiktok\.com/i,
214
+ /@[^\/]+\/video\/(\d+)/
215
+ ]
216
+ };
217
+ // Search in iframes
218
+ const iframes = document.querySelectorAll('iframe');
219
+ iframes.forEach((iframe) => {
220
+ const src = iframe.src;
221
+ if (!src)
222
+ return;
223
+ for (const [platform, patterns] of Object.entries(hostingPatterns)) {
224
+ if (platforms.includes('all') || platforms.includes(platform)) {
225
+ if (patterns[0].test(src)) {
226
+ const videoId = src.match(patterns[1] || patterns[0]);
227
+ sources.push({
228
+ platform,
229
+ url: src,
230
+ videoId: videoId ? videoId[1] : null,
231
+ type: 'iframe',
232
+ embedCode: extractEmbedCodes ? iframe.outerHTML : null
233
+ });
234
+ }
235
+ }
236
+ }
237
+ });
238
+ // Search in links
239
+ const links = document.querySelectorAll('a[href]');
240
+ links.forEach((link) => {
241
+ const href = link.href;
242
+ for (const [platform, patterns] of Object.entries(hostingPatterns)) {
243
+ if (platforms.includes('all') || platforms.includes(platform)) {
244
+ if (patterns[0].test(href)) {
245
+ const videoId = href.match(patterns[1] || patterns[0]);
246
+ sources.push({
247
+ platform,
248
+ url: href,
249
+ videoId: videoId ? videoId[1] : null,
250
+ type: 'link',
251
+ linkText: link.textContent?.trim() || ''
252
+ });
253
+ }
254
+ }
255
+ }
256
+ });
257
+ // Search in script content for API calls and video IDs
258
+ const scripts = document.querySelectorAll('script');
259
+ scripts.forEach((script) => {
260
+ const content = script.textContent || '';
261
+ for (const [platform, patterns] of Object.entries(hostingPatterns)) {
262
+ if (platforms.includes('all') || platforms.includes(platform)) {
263
+ const matches = content.match(patterns[1] || patterns[0]);
264
+ if (matches) {
265
+ matches.forEach(match => {
266
+ sources.push({
267
+ platform,
268
+ url: match,
269
+ type: 'script',
270
+ source: 'javascript'
271
+ });
272
+ });
273
+ }
274
+ }
275
+ }
276
+ });
277
+ return sources;
278
+ }, { platforms, extractEmbedCodes, extractAPIKeys });
279
+ return {
280
+ success: true,
281
+ sources: hostingSources,
282
+ platforms: [...new Set(hostingSources.map((s) => s.platform))],
283
+ total: hostingSources.length,
284
+ message: `Found ${hostingSources.length} hosting platform sources`
285
+ };
286
+ }, 'Failed to find video hosting sources');
287
+ });
288
+ }
289
+ // AJAX Finders Handler
290
+ export async function handleAjaxFinders(args) {
291
+ return withWorkflowValidation('ajax_finders', args, async () => {
292
+ return withErrorHandling(async () => {
293
+ const browser = getBrowser();
294
+ const page = getPage();
295
+ if (!browser || !page) {
296
+ throw new Error('Browser not initialized. Call browser_init first.');
297
+ }
298
+ const { interceptXHR = true, interceptFetch = true, recordResponses = true, filterByContentType = ['application/json', 'text/html', 'application/xml'], monitoringDuration = 10000 } = args;
299
+ const ajaxRequests = [];
300
+ // Intercept network requests
301
+ await page.setRequestInterception(true);
302
+ page.on('request', (request) => {
303
+ const resourceType = request.resourceType();
304
+ if (resourceType === 'xhr' || resourceType === 'fetch') {
305
+ ajaxRequests.push({
306
+ url: request.url(),
307
+ method: request.method(),
308
+ headers: request.headers(),
309
+ postData: request.postData(),
310
+ resourceType,
311
+ timestamp: Date.now(),
312
+ type: 'request'
313
+ });
314
+ }
315
+ request.continue();
316
+ });
317
+ if (recordResponses) {
318
+ page.on('response', async (response) => {
319
+ try {
320
+ const request = response.request();
321
+ const resourceType = request.resourceType();
322
+ if (resourceType === 'xhr' || resourceType === 'fetch') {
323
+ const contentType = response.headers()['content-type'] || '';
324
+ const shouldRecord = filterByContentType.length === 0 ||
325
+ filterByContentType.some((type) => contentType.includes(type));
326
+ if (shouldRecord) {
327
+ let responseBody = '';
328
+ try {
329
+ responseBody = await response.text();
330
+ }
331
+ catch (e) {
332
+ responseBody = '[Unable to read response body]';
333
+ }
334
+ ajaxRequests.push({
335
+ url: response.url(),
336
+ status: response.status(),
337
+ headers: response.headers(),
338
+ contentType,
339
+ body: responseBody.substring(0, 10000), // Limit body size
340
+ timestamp: Date.now(),
341
+ type: 'response'
342
+ });
343
+ }
344
+ }
345
+ }
346
+ catch (error) {
347
+ // Ignore response processing errors
348
+ }
349
+ });
350
+ }
351
+ // Wait for AJAX activity
352
+ await new Promise(resolve => setTimeout(resolve, monitoringDuration));
353
+ await page.setRequestInterception(false);
354
+ // Group requests and responses
355
+ const groupedRequests = ajaxRequests.reduce((acc, item) => {
356
+ const key = item.url;
357
+ if (!acc[key])
358
+ acc[key] = { requests: [], responses: [] };
359
+ if (item.type === 'request') {
360
+ acc[key].requests.push(item);
361
+ }
362
+ else {
363
+ acc[key].responses.push(item);
364
+ }
365
+ return acc;
366
+ }, {});
367
+ return {
368
+ success: true,
369
+ ajaxRequests: groupedRequests,
370
+ totalRequests: ajaxRequests.filter(r => r.type === 'request').length,
371
+ totalResponses: ajaxRequests.filter(r => r.type === 'response').length,
372
+ uniqueEndpoints: Object.keys(groupedRequests).length,
373
+ message: `Monitored AJAX activity for ${monitoringDuration}ms`
374
+ };
375
+ }, 'Failed to monitor AJAX requests');
376
+ });
377
+ }
378
+ // User Agent Finders Handler
379
+ export async function handleUserAgentFinders(args) {
380
+ return withWorkflowValidation('user_agent_finders', args, async () => {
381
+ return withErrorHandling(async () => {
382
+ const browser = getBrowser();
383
+ const page = getPage();
384
+ if (!browser || !page) {
385
+ throw new Error('Browser not initialized. Call browser_init first.');
386
+ }
387
+ const { detectFingerprinting = true, analyzeHeaders = true, findCustomUA = true, trackingDetection = true } = args;
388
+ const userAgentInfo = await page.evaluate((options) => {
389
+ const { detectFingerprinting, analyzeHeaders, findCustomUA, trackingDetection } = options;
390
+ const info = {};
391
+ // Get current user agent
392
+ info.currentUserAgent = navigator.userAgent;
393
+ info.platform = navigator.platform;
394
+ info.language = navigator.language;
395
+ info.languages = navigator.languages;
396
+ // Detect fingerprinting attempts
397
+ if (detectFingerprinting) {
398
+ info.fingerprinting = {
399
+ canvas: !!document.createElement('canvas').getContext,
400
+ webgl: !!document.createElement('canvas').getContext('webgl'),
401
+ audioContext: !!window.AudioContext || !!window.webkitAudioContext,
402
+ webrtc: !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection),
403
+ deviceMemory: navigator.deviceMemory || 'unknown',
404
+ hardwareConcurrency: navigator.hardwareConcurrency || 'unknown',
405
+ maxTouchPoints: navigator.maxTouchPoints || 0
406
+ };
407
+ }
408
+ // Check for custom UA requirements in scripts
409
+ if (findCustomUA) {
410
+ const scripts = document.querySelectorAll('script');
411
+ const uaRequirements = [];
412
+ scripts.forEach(script => {
413
+ const content = script.textContent || '';
414
+ // Look for User-Agent checks
415
+ const uaPatterns = [
416
+ /userAgent\s*[=!]==?\s*['"]([^'"]+)['"]/gi,
417
+ /navigator\.userAgent\.indexOf\(['"]([^'"]+)['"]\)/gi,
418
+ /User-Agent:\s*['"]([^'"]+)['"]/gi
419
+ ];
420
+ uaPatterns.forEach(pattern => {
421
+ const matches = content.match(pattern);
422
+ if (matches) {
423
+ uaRequirements.push(...matches);
424
+ }
425
+ });
426
+ });
427
+ info.customUARequirements = [...new Set(uaRequirements)];
428
+ }
429
+ return info;
430
+ }, { detectFingerprinting, analyzeHeaders, findCustomUA, trackingDetection });
431
+ // Get actual request headers from network
432
+ const requestHeaders = {};
433
+ if (analyzeHeaders) {
434
+ await page.setRequestInterception(true);
435
+ const headerPromise = new Promise((resolve) => {
436
+ page.once('request', (request) => {
437
+ resolve(request.headers());
438
+ request.continue();
439
+ });
440
+ });
441
+ // Trigger a request to capture headers
442
+ await page.reload({ waitUntil: 'domcontentloaded' });
443
+ const headers = await headerPromise;
444
+ requestHeaders.actual = headers;
445
+ await page.setRequestInterception(false);
446
+ }
447
+ return {
448
+ success: true,
449
+ userAgentInfo,
450
+ requestHeaders,
451
+ message: 'User-Agent analysis completed'
452
+ };
453
+ }, 'Failed to analyze User-Agent');
454
+ });
455
+ }
456
+ // Export handlers for all specialized tools
457
+ export const specializedToolsHandlers = {
458
+ links_finders: handleLinksFinders,
459
+ video_play_sources_finder: handleVideoPlaySourcesFinder,
460
+ video_player_hostars_sources_finder: handleVideoPlayerHostarsSourcesFinder,
461
+ ajax_finders: handleAjaxFinders,
462
+ user_agent_finders: handleUserAgentFinders,
463
+ // Add more handlers as needed...
464
+ };