brave-real-browser-mcp-server 2.11.1 → 2.11.2
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/dist/optimization-utils.js +479 -0
- package/package.json +1 -1
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
// Optimization Utilities for All MCP Tools
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import { sleep } from './system-utils.js';
|
|
4
|
+
/**
|
|
5
|
+
* Tool Optimization Configuration
|
|
6
|
+
* Centralized defaults for all 113 tools
|
|
7
|
+
*/
|
|
8
|
+
export const TOOL_OPTIMIZATION_CONFIG = {
|
|
9
|
+
// Network Monitoring Tools (9 tools)
|
|
10
|
+
networkMonitoring: {
|
|
11
|
+
defaultDuration: 10000,
|
|
12
|
+
maxDuration: 30000,
|
|
13
|
+
monitoringTimeout: 15000,
|
|
14
|
+
retryAttempts: 3,
|
|
15
|
+
retryDelay: 2000,
|
|
16
|
+
},
|
|
17
|
+
// Video Extraction Tools (30+ tools)
|
|
18
|
+
videoExtraction: {
|
|
19
|
+
iframeDepthLimit: 5,
|
|
20
|
+
defaultTimeout: 12000,
|
|
21
|
+
hosterPatterns: [
|
|
22
|
+
'vidcloud',
|
|
23
|
+
'vidsrc',
|
|
24
|
+
'filemoon',
|
|
25
|
+
'streamtape',
|
|
26
|
+
'doodstream',
|
|
27
|
+
'mixdrop',
|
|
28
|
+
'upstream',
|
|
29
|
+
'streamwish',
|
|
30
|
+
'vidmoly',
|
|
31
|
+
'vidlox',
|
|
32
|
+
'mystream',
|
|
33
|
+
'cloudemb',
|
|
34
|
+
'embedsrc',
|
|
35
|
+
'upns.online',
|
|
36
|
+
'voe.sx',
|
|
37
|
+
'streamlare',
|
|
38
|
+
'dailymotion',
|
|
39
|
+
'youtube',
|
|
40
|
+
'vimeo',
|
|
41
|
+
'twitch',
|
|
42
|
+
],
|
|
43
|
+
videoSelectors: [
|
|
44
|
+
'video',
|
|
45
|
+
'iframe[src*="player"]',
|
|
46
|
+
'iframe[src*="embed"]',
|
|
47
|
+
'div[class*="player"]',
|
|
48
|
+
'div[id*="player"]',
|
|
49
|
+
'[data-video-url]',
|
|
50
|
+
'[data-src*=".mp4"]',
|
|
51
|
+
'[data-src*=".m3u8"]',
|
|
52
|
+
'[data-src*=".mkv"]',
|
|
53
|
+
'[data-src*=".webm"]',
|
|
54
|
+
],
|
|
55
|
+
downloadButtonSelectors: [
|
|
56
|
+
'a[download]',
|
|
57
|
+
'button[download]',
|
|
58
|
+
'a[href*="download"]',
|
|
59
|
+
'button[data-download]',
|
|
60
|
+
'a[href*=".mp4"]',
|
|
61
|
+
'a[href*=".mkv"]',
|
|
62
|
+
'a[href*=".webm"]',
|
|
63
|
+
'a[class*="download"]',
|
|
64
|
+
'.download-btn',
|
|
65
|
+
'.btn-download',
|
|
66
|
+
'[class*="download"]',
|
|
67
|
+
'[id*="download"]',
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
// Data Extraction Tools (40+ tools)
|
|
71
|
+
dataExtraction: {
|
|
72
|
+
deepScanEnabled: true,
|
|
73
|
+
recursiveSearchDepth: 5,
|
|
74
|
+
deduplicationEnabled: true,
|
|
75
|
+
cachingEnabled: true,
|
|
76
|
+
cacheTTL: 300000, // 5 minutes
|
|
77
|
+
defaultTimeout: 10000,
|
|
78
|
+
},
|
|
79
|
+
// Redirect Tracing
|
|
80
|
+
redirectTracing: {
|
|
81
|
+
maxRedirects: 15,
|
|
82
|
+
redirectTimeout: 30000,
|
|
83
|
+
followIframes: true,
|
|
84
|
+
maxDepth: 5,
|
|
85
|
+
},
|
|
86
|
+
// Global Settings
|
|
87
|
+
global: {
|
|
88
|
+
defaultTimeout: 10000,
|
|
89
|
+
retryAttempts: 3,
|
|
90
|
+
retryDelay: 2000,
|
|
91
|
+
enableErrorLogging: true,
|
|
92
|
+
enableCaching: true,
|
|
93
|
+
enableDeduplication: true,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Result Deduplication - Remove duplicate entries
|
|
98
|
+
*/
|
|
99
|
+
export function deduplicateResults(results, uniqueKey = 'url') {
|
|
100
|
+
const seen = new Set();
|
|
101
|
+
return results.filter((item) => {
|
|
102
|
+
const key = item[uniqueKey] || JSON.stringify(item);
|
|
103
|
+
if (seen.has(key))
|
|
104
|
+
return false;
|
|
105
|
+
seen.add(key);
|
|
106
|
+
return true;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Deep Deduplication - For complex objects
|
|
111
|
+
*/
|
|
112
|
+
export function deepDeduplicateResults(results) {
|
|
113
|
+
const seen = new Set();
|
|
114
|
+
return results.filter((item) => {
|
|
115
|
+
const key = JSON.stringify(item);
|
|
116
|
+
if (seen.has(key))
|
|
117
|
+
return false;
|
|
118
|
+
seen.add(key);
|
|
119
|
+
return true;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Result Caching with TTL
|
|
124
|
+
*/
|
|
125
|
+
export class ResultCache {
|
|
126
|
+
cache = new Map();
|
|
127
|
+
set(key, data, ttl = 300000) {
|
|
128
|
+
this.cache.set(key, {
|
|
129
|
+
data,
|
|
130
|
+
timestamp: Date.now(),
|
|
131
|
+
ttl,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
get(key) {
|
|
135
|
+
const cached = this.cache.get(key);
|
|
136
|
+
if (!cached)
|
|
137
|
+
return null;
|
|
138
|
+
const age = Date.now() - cached.timestamp;
|
|
139
|
+
if (age > cached.ttl) {
|
|
140
|
+
this.cache.delete(key);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return cached.data;
|
|
144
|
+
}
|
|
145
|
+
clear() {
|
|
146
|
+
this.cache.clear();
|
|
147
|
+
}
|
|
148
|
+
getStats() {
|
|
149
|
+
return {
|
|
150
|
+
size: this.cache.size,
|
|
151
|
+
entries: Array.from(this.cache.keys()),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Global cache instance for all tools
|
|
157
|
+
*/
|
|
158
|
+
export const globalCache = new ResultCache();
|
|
159
|
+
/**
|
|
160
|
+
* Retry with Exponential Backoff
|
|
161
|
+
*/
|
|
162
|
+
export async function retryWithBackoff(fn, maxAttempts = 3, initialDelay = 1000) {
|
|
163
|
+
let lastError;
|
|
164
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
165
|
+
try {
|
|
166
|
+
return await fn();
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
170
|
+
console.error(`Attempt ${attempt}/${maxAttempts} failed:`, lastError.message);
|
|
171
|
+
if (attempt < maxAttempts) {
|
|
172
|
+
const delay = initialDelay * Math.pow(2, attempt - 1);
|
|
173
|
+
await sleep(delay);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
throw lastError;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Enhanced Error Handler for Tools
|
|
181
|
+
*/
|
|
182
|
+
export function createErrorHandler(toolName) {
|
|
183
|
+
return {
|
|
184
|
+
handle(error, context = '') {
|
|
185
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
186
|
+
console.error(`[${toolName}] Error in ${context}: ${message}`);
|
|
187
|
+
return {
|
|
188
|
+
content: [
|
|
189
|
+
{
|
|
190
|
+
type: 'text',
|
|
191
|
+
text: `❌ ${toolName} Error: ${message}`,
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
isError: true,
|
|
195
|
+
toolName,
|
|
196
|
+
context,
|
|
197
|
+
};
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Tool Execution Wrapper with Timeout and Retry
|
|
203
|
+
*/
|
|
204
|
+
export async function executeToolWithOptimizations(toolName, toolFn, options = {}) {
|
|
205
|
+
const { timeout = TOOL_OPTIMIZATION_CONFIG.global.defaultTimeout, retryAttempts = TOOL_OPTIMIZATION_CONFIG.global.retryAttempts, retryDelay = TOOL_OPTIMIZATION_CONFIG.global.retryDelay, cacheKey, shouldCache = TOOL_OPTIMIZATION_CONFIG.global.enableCaching, } = options;
|
|
206
|
+
// Check cache first
|
|
207
|
+
if (cacheKey && shouldCache) {
|
|
208
|
+
const cached = globalCache.get(cacheKey);
|
|
209
|
+
if (cached) {
|
|
210
|
+
console.log(`[${toolName}] Cache hit for key: ${cacheKey}`);
|
|
211
|
+
return cached;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
// Execute with timeout
|
|
216
|
+
const result = await Promise.race([
|
|
217
|
+
toolFn(),
|
|
218
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`${toolName} timeout after ${timeout}ms`)), timeout)),
|
|
219
|
+
]);
|
|
220
|
+
// Cache result if enabled
|
|
221
|
+
if (cacheKey && shouldCache) {
|
|
222
|
+
globalCache.set(cacheKey, result);
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
// Retry on failure for network tools
|
|
228
|
+
if (retryAttempts > 0) {
|
|
229
|
+
console.log(`[${toolName}] Retrying after error: ${error instanceof Error ? error.message : String(error)}`);
|
|
230
|
+
return retryWithBackoff(toolFn, retryAttempts, retryDelay);
|
|
231
|
+
}
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Resource Cleanup for Tools
|
|
237
|
+
*/
|
|
238
|
+
export async function cleanupToolResources(page, context) {
|
|
239
|
+
try {
|
|
240
|
+
// Remove all event listeners
|
|
241
|
+
if (page) {
|
|
242
|
+
await page.removeAllListeners?.();
|
|
243
|
+
}
|
|
244
|
+
// Close unnecessary tabs
|
|
245
|
+
if (context) {
|
|
246
|
+
const pages = await context.pages?.();
|
|
247
|
+
if (pages && pages.length > 1) {
|
|
248
|
+
for (const p of pages.slice(1)) {
|
|
249
|
+
await p.close?.();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Clear cache periodically
|
|
254
|
+
const cacheStats = globalCache.getStats();
|
|
255
|
+
if (cacheStats.size > 100) {
|
|
256
|
+
console.log('[Cleanup] Clearing cache - size exceeded 100 items');
|
|
257
|
+
globalCache.clear();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
console.error('[Cleanup] Error during cleanup:', error);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Video Hoster Detection Database
|
|
266
|
+
*/
|
|
267
|
+
export const VIDEO_HOSTERS_DB = {
|
|
268
|
+
primary: [
|
|
269
|
+
{ name: 'vidcloud', patterns: ['vidcloud', 'vidnode'], embedPattern: '/embed/' },
|
|
270
|
+
{ name: 'vidsrc', patterns: ['vidsrc.to', 'vidsrc.me', 'vidsrc.xyz'] },
|
|
271
|
+
{ name: 'filemoon', patterns: ['filemoon.sx', 'filemoon.to'] },
|
|
272
|
+
{ name: 'streamtape', patterns: ['streamtape.com', 'streamtape.to'] },
|
|
273
|
+
{ name: 'doodstream', patterns: ['dood.', 'doodstream.'] },
|
|
274
|
+
{ name: 'mixdrop', patterns: ['mixdrop.co', 'mixdrop.to'] },
|
|
275
|
+
{ name: 'upstream', patterns: ['upstream.to', 'upstreamcdn'] },
|
|
276
|
+
{ name: 'streamwish', patterns: ['streamwish.to', 'streamwish.com'] },
|
|
277
|
+
],
|
|
278
|
+
methods: {
|
|
279
|
+
iframe: { depth: 5, timeout: 12000 },
|
|
280
|
+
directLink: { patterns: ['.m3u8', '.mp4', '.mkv', '/stream/', '.webm'] },
|
|
281
|
+
apiEndpoint: { patterns: ['/api/source', '/api/video', '/get-source', '/embed'] },
|
|
282
|
+
},
|
|
283
|
+
isKnownHoster(url) {
|
|
284
|
+
const lowerUrl = url.toLowerCase();
|
|
285
|
+
for (const hoster of this.primary) {
|
|
286
|
+
if (hoster.patterns.some((p) => lowerUrl.includes(p))) {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return false;
|
|
291
|
+
},
|
|
292
|
+
getHosterName(url) {
|
|
293
|
+
const lowerUrl = url.toLowerCase();
|
|
294
|
+
for (const hoster of this.primary) {
|
|
295
|
+
if (hoster.patterns.some((p) => lowerUrl.includes(p))) {
|
|
296
|
+
return hoster.name;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return null;
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
/**
|
|
303
|
+
* Common Selector Utilities
|
|
304
|
+
*/
|
|
305
|
+
export const SELECTOR_UTILS = {
|
|
306
|
+
// Link selectors
|
|
307
|
+
linkSelectors: [
|
|
308
|
+
'a[href]',
|
|
309
|
+
'a[href^="http"]',
|
|
310
|
+
'a[href^="/"]',
|
|
311
|
+
'[data-href]',
|
|
312
|
+
'button[onclick*="http"]',
|
|
313
|
+
],
|
|
314
|
+
// Image selectors
|
|
315
|
+
imageSelectors: [
|
|
316
|
+
'img',
|
|
317
|
+
'img[src]',
|
|
318
|
+
'picture img',
|
|
319
|
+
'[style*="background-image"]',
|
|
320
|
+
'div[data-src*="image"]',
|
|
321
|
+
'div[data-background*="image"]',
|
|
322
|
+
],
|
|
323
|
+
// Video selectors: Already defined above
|
|
324
|
+
videoSelectors: TOOL_OPTIMIZATION_CONFIG.videoExtraction.videoSelectors,
|
|
325
|
+
// Download button selectors: Already defined above
|
|
326
|
+
downloadSelectors: TOOL_OPTIMIZATION_CONFIG.videoExtraction.downloadButtonSelectors,
|
|
327
|
+
// Form selectors
|
|
328
|
+
formSelectors: ['form', 'input', 'textarea', 'select', 'button[type="submit"]'],
|
|
329
|
+
// Table selectors
|
|
330
|
+
tableSelectors: ['table', 'tbody tr', 'thead tr', '.table-row', '[role="table"]'],
|
|
331
|
+
// Pagination selectors
|
|
332
|
+
paginationSelectors: [
|
|
333
|
+
'a[rel="next"]',
|
|
334
|
+
'button[aria-label*="next"]',
|
|
335
|
+
'.pagination a',
|
|
336
|
+
'.next',
|
|
337
|
+
'a[href*="page"]',
|
|
338
|
+
'a[href*="offset"]',
|
|
339
|
+
],
|
|
340
|
+
// Hidden element detection
|
|
341
|
+
hiddenSelectors: [
|
|
342
|
+
'[style*="display: none"]',
|
|
343
|
+
'[style*="visibility: hidden"]',
|
|
344
|
+
'[hidden]',
|
|
345
|
+
'.hidden',
|
|
346
|
+
'.d-none',
|
|
347
|
+
],
|
|
348
|
+
// All selectors
|
|
349
|
+
getAllSelectors() {
|
|
350
|
+
return {
|
|
351
|
+
links: this.linkSelectors,
|
|
352
|
+
images: this.imageSelectors,
|
|
353
|
+
videos: this.videoSelectors,
|
|
354
|
+
downloads: this.downloadSelectors,
|
|
355
|
+
forms: this.formSelectors,
|
|
356
|
+
tables: this.tableSelectors,
|
|
357
|
+
pagination: this.paginationSelectors,
|
|
358
|
+
hidden: this.hiddenSelectors,
|
|
359
|
+
};
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
/**
|
|
363
|
+
* Performance Metrics Collection
|
|
364
|
+
*/
|
|
365
|
+
export class PerformanceMetrics {
|
|
366
|
+
metrics = new Map();
|
|
367
|
+
start(toolName) {
|
|
368
|
+
this.metrics.set(toolName, { start: Date.now() });
|
|
369
|
+
}
|
|
370
|
+
end(toolName) {
|
|
371
|
+
const metric = this.metrics.get(toolName);
|
|
372
|
+
if (metric) {
|
|
373
|
+
metric.end = Date.now();
|
|
374
|
+
metric.duration = metric.end - metric.start;
|
|
375
|
+
return metric.duration;
|
|
376
|
+
}
|
|
377
|
+
return 0;
|
|
378
|
+
}
|
|
379
|
+
getMetrics(toolName) {
|
|
380
|
+
return this.metrics.get(toolName) || null;
|
|
381
|
+
}
|
|
382
|
+
getAllMetrics() {
|
|
383
|
+
return this.metrics;
|
|
384
|
+
}
|
|
385
|
+
getAverageDuration() {
|
|
386
|
+
let totalDuration = 0;
|
|
387
|
+
let count = 0;
|
|
388
|
+
for (const metric of this.metrics.values()) {
|
|
389
|
+
if (metric.duration) {
|
|
390
|
+
totalDuration += metric.duration;
|
|
391
|
+
count++;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return count > 0 ? totalDuration / count : 0;
|
|
395
|
+
}
|
|
396
|
+
reset() {
|
|
397
|
+
this.metrics.clear();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Global metrics instance
|
|
402
|
+
*/
|
|
403
|
+
export const globalMetrics = new PerformanceMetrics();
|
|
404
|
+
export class ToolStatusTracker {
|
|
405
|
+
tools = new Map();
|
|
406
|
+
register(toolName, category, optimizations = []) {
|
|
407
|
+
this.tools.set(toolName, {
|
|
408
|
+
name: toolName,
|
|
409
|
+
category,
|
|
410
|
+
status: 'pending',
|
|
411
|
+
optimizations,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
updateStatus(toolName, status, performanceGain) {
|
|
415
|
+
const tool = this.tools.get(toolName);
|
|
416
|
+
if (tool) {
|
|
417
|
+
tool.status = status;
|
|
418
|
+
if (performanceGain !== undefined) {
|
|
419
|
+
tool.performanceGain = performanceGain;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
recordExecution(toolName, duration) {
|
|
424
|
+
const tool = this.tools.get(toolName);
|
|
425
|
+
if (tool) {
|
|
426
|
+
tool.lastExecutionTime = duration;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
getStatus(toolName) {
|
|
430
|
+
return this.tools.get(toolName) || null;
|
|
431
|
+
}
|
|
432
|
+
getAllStatus() {
|
|
433
|
+
return Array.from(this.tools.values());
|
|
434
|
+
}
|
|
435
|
+
getSummary() {
|
|
436
|
+
let optimized = 0;
|
|
437
|
+
let pending = 0;
|
|
438
|
+
let failed = 0;
|
|
439
|
+
const byCategory = new Map();
|
|
440
|
+
for (const tool of this.tools.values()) {
|
|
441
|
+
if (tool.status === 'optimized')
|
|
442
|
+
optimized++;
|
|
443
|
+
else if (tool.status === 'pending')
|
|
444
|
+
pending++;
|
|
445
|
+
else if (tool.status === 'failed')
|
|
446
|
+
failed++;
|
|
447
|
+
const count = byCategory.get(tool.category) || 0;
|
|
448
|
+
byCategory.set(tool.category, count + 1);
|
|
449
|
+
}
|
|
450
|
+
return {
|
|
451
|
+
total: this.tools.size,
|
|
452
|
+
optimized,
|
|
453
|
+
pending,
|
|
454
|
+
failed,
|
|
455
|
+
byCategory,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Global tool status tracker
|
|
461
|
+
*/
|
|
462
|
+
export const globalToolStatus = new ToolStatusTracker();
|
|
463
|
+
export default {
|
|
464
|
+
TOOL_OPTIMIZATION_CONFIG,
|
|
465
|
+
deduplicateResults,
|
|
466
|
+
deepDeduplicateResults,
|
|
467
|
+
ResultCache,
|
|
468
|
+
globalCache,
|
|
469
|
+
retryWithBackoff,
|
|
470
|
+
createErrorHandler,
|
|
471
|
+
executeToolWithOptimizations,
|
|
472
|
+
cleanupToolResources,
|
|
473
|
+
VIDEO_HOSTERS_DB,
|
|
474
|
+
SELECTOR_UTILS,
|
|
475
|
+
PerformanceMetrics,
|
|
476
|
+
globalMetrics,
|
|
477
|
+
ToolStatusTracker,
|
|
478
|
+
globalToolStatus,
|
|
479
|
+
};
|