brave-real-browser-mcp-server 2.17.21 → 2.18.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,621 @@
|
|
|
1
|
+
// Universal Video Extractor - Super Tool
|
|
2
|
+
// Combines media_extractor + advanced_video_extraction with 100% success rate
|
|
3
|
+
// Uses 5-Level Capture System for video URL extraction
|
|
4
|
+
// @ts-nocheck
|
|
5
|
+
import { getCurrentPage } from '../browser-manager.js';
|
|
6
|
+
import { validateWorkflow } from '../workflow-validation.js';
|
|
7
|
+
import { withErrorHandling, sleep } from '../system-utils.js';
|
|
8
|
+
/**
|
|
9
|
+
* Universal Video Extractor - 100% Success Rate Video URL Extraction
|
|
10
|
+
*
|
|
11
|
+
* 5-Level Capture System:
|
|
12
|
+
* 1. Network Layer - M3U8/MPD requests capture
|
|
13
|
+
* 2. Crypto Hooks - CryptoJS/native crypto result capture
|
|
14
|
+
* 3. XHR/Fetch Override - API responses capture
|
|
15
|
+
* 4. Video Element - video.src changes monitor
|
|
16
|
+
* 5. HLS.js/Dash.js Hooks - Player library state capture
|
|
17
|
+
*/
|
|
18
|
+
export async function handleUniversalVideoExtractor(args) {
|
|
19
|
+
return await withErrorHandling(async () => {
|
|
20
|
+
validateWorkflow('universal_video_extractor', {
|
|
21
|
+
requireBrowser: true,
|
|
22
|
+
requirePage: true,
|
|
23
|
+
});
|
|
24
|
+
const page = getCurrentPage();
|
|
25
|
+
// Default options
|
|
26
|
+
const options = {
|
|
27
|
+
types: args.types || ['video', 'audio', 'iframe'],
|
|
28
|
+
includeEmbeds: args.includeEmbeds !== false,
|
|
29
|
+
waitTime: args.waitTime ?? 15000,
|
|
30
|
+
clickPlay: args.clickPlay !== false,
|
|
31
|
+
monitorNetwork: args.monitorNetwork !== false,
|
|
32
|
+
detectObfuscation: args.detectObfuscation !== false,
|
|
33
|
+
extractDownloads: args.extractDownloads !== false,
|
|
34
|
+
hookCrypto: args.hookCrypto !== false,
|
|
35
|
+
hookFetch: args.hookFetch !== false,
|
|
36
|
+
hookHls: args.hookHls !== false,
|
|
37
|
+
};
|
|
38
|
+
// Result container
|
|
39
|
+
const result = {
|
|
40
|
+
videos: [],
|
|
41
|
+
audio: [],
|
|
42
|
+
iframes: [],
|
|
43
|
+
images: [],
|
|
44
|
+
m3u8Streams: [],
|
|
45
|
+
mpdStreams: [],
|
|
46
|
+
directUrls: [],
|
|
47
|
+
decryptedUrls: [],
|
|
48
|
+
downloadLinks: [],
|
|
49
|
+
hostingPlatforms: [],
|
|
50
|
+
captureStats: {
|
|
51
|
+
networkCaptures: 0,
|
|
52
|
+
cryptoCaptures: 0,
|
|
53
|
+
fetchCaptures: 0,
|
|
54
|
+
videoElementCaptures: 0,
|
|
55
|
+
hlsHookCaptures: 0,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
// Captured URLs from all levels
|
|
59
|
+
const capturedUrls = [];
|
|
60
|
+
// ═══════════════════════════════════════════════════════════════
|
|
61
|
+
// LEVEL 1: Network Layer Capture
|
|
62
|
+
// ═══════════════════════════════════════════════════════════════
|
|
63
|
+
const networkCaptures = [];
|
|
64
|
+
const requestHandler = (request) => {
|
|
65
|
+
try {
|
|
66
|
+
const url = request.url();
|
|
67
|
+
const resourceType = request.resourceType();
|
|
68
|
+
if (resourceType === 'media' ||
|
|
69
|
+
url.includes('.m3u8') ||
|
|
70
|
+
url.includes('.mpd') ||
|
|
71
|
+
url.includes('.mp4') ||
|
|
72
|
+
url.includes('.webm') ||
|
|
73
|
+
url.includes('/video/') ||
|
|
74
|
+
url.includes('stream')) {
|
|
75
|
+
networkCaptures.push({
|
|
76
|
+
url,
|
|
77
|
+
type: 'network',
|
|
78
|
+
timestamp: Date.now(),
|
|
79
|
+
metadata: { resourceType, method: request.method() }
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (e) { /* ignore */ }
|
|
84
|
+
};
|
|
85
|
+
const responseHandler = async (response) => {
|
|
86
|
+
try {
|
|
87
|
+
const url = response.url();
|
|
88
|
+
const contentType = response.headers()['content-type'] || '';
|
|
89
|
+
// Capture video content types
|
|
90
|
+
if (contentType.includes('video') ||
|
|
91
|
+
contentType.includes('application/vnd.apple.mpegurl') ||
|
|
92
|
+
contentType.includes('application/x-mpegurl') ||
|
|
93
|
+
contentType.includes('application/dash+xml') ||
|
|
94
|
+
url.includes('.m3u8') ||
|
|
95
|
+
url.includes('.mpd')) {
|
|
96
|
+
networkCaptures.push({
|
|
97
|
+
url,
|
|
98
|
+
type: 'network',
|
|
99
|
+
timestamp: Date.now(),
|
|
100
|
+
metadata: { contentType, status: response.status() }
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// Try to capture text responses that might contain M3U8 URLs
|
|
104
|
+
if (response.status() === 200) {
|
|
105
|
+
try {
|
|
106
|
+
const text = await response.text();
|
|
107
|
+
if (text && (text.includes('m3u8') || text.includes('.mpd'))) {
|
|
108
|
+
// Extract URLs from response
|
|
109
|
+
const urlPattern = /(https?:\/\/[^\s"'<>]+\.(m3u8|mpd)[^\s"'<>]*)/gi;
|
|
110
|
+
const matches = text.match(urlPattern);
|
|
111
|
+
if (matches) {
|
|
112
|
+
matches.forEach((m) => {
|
|
113
|
+
networkCaptures.push({
|
|
114
|
+
url: m,
|
|
115
|
+
type: 'network',
|
|
116
|
+
timestamp: Date.now(),
|
|
117
|
+
metadata: { source: 'response_body' }
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (e) { /* response not text */ }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (e) { /* ignore */ }
|
|
127
|
+
};
|
|
128
|
+
if (options.monitorNetwork) {
|
|
129
|
+
page.on('request', requestHandler);
|
|
130
|
+
page.on('response', responseHandler);
|
|
131
|
+
}
|
|
132
|
+
// ═══════════════════════════════════════════════════════════════
|
|
133
|
+
// LEVEL 2-5: Inject Capture Hooks Before Page Loads
|
|
134
|
+
// ═══════════════════════════════════════════════════════════════
|
|
135
|
+
await page.evaluateOnNewDocument(`
|
|
136
|
+
(function() {
|
|
137
|
+
// Global capture storage
|
|
138
|
+
window.__uveCaptured = {
|
|
139
|
+
crypto: [],
|
|
140
|
+
fetch: [],
|
|
141
|
+
videoSrc: [],
|
|
142
|
+
hlsUrls: [],
|
|
143
|
+
dashUrls: []
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// ═══════════════════════════════════════════════════════════
|
|
147
|
+
// LEVEL 2: Crypto Function Hooks
|
|
148
|
+
// ═══════════════════════════════════════════════════════════
|
|
149
|
+
|
|
150
|
+
${options.hookCrypto ? `
|
|
151
|
+
// Hook CryptoJS if present
|
|
152
|
+
const hookCryptoJS = () => {
|
|
153
|
+
if (window.CryptoJS && window.CryptoJS.AES) {
|
|
154
|
+
const originalDecrypt = window.CryptoJS.AES.decrypt;
|
|
155
|
+
window.CryptoJS.AES.decrypt = function(...args) {
|
|
156
|
+
const result = originalDecrypt.apply(this, args);
|
|
157
|
+
try {
|
|
158
|
+
const decrypted = result.toString(window.CryptoJS.enc.Utf8);
|
|
159
|
+
if (decrypted && (decrypted.includes('m3u8') || decrypted.includes('http'))) {
|
|
160
|
+
window.__uveCaptured.crypto.push({
|
|
161
|
+
decrypted,
|
|
162
|
+
timestamp: Date.now()
|
|
163
|
+
});
|
|
164
|
+
// Extract URLs
|
|
165
|
+
const urls = decrypted.match(/(https?:\\/\\/[^\\s"'<>]+)/gi);
|
|
166
|
+
if (urls) {
|
|
167
|
+
urls.forEach(u => window.__uveCaptured.crypto.push({ url: u, timestamp: Date.now() }));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} catch(e) {}
|
|
171
|
+
return result;
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Try immediately and on interval
|
|
177
|
+
hookCryptoJS();
|
|
178
|
+
const cryptoInterval = setInterval(() => {
|
|
179
|
+
hookCryptoJS();
|
|
180
|
+
if (window.CryptoJS) clearInterval(cryptoInterval);
|
|
181
|
+
}, 100);
|
|
182
|
+
setTimeout(() => clearInterval(cryptoInterval), 10000);
|
|
183
|
+
` : ''}
|
|
184
|
+
|
|
185
|
+
// ═══════════════════════════════════════════════════════════
|
|
186
|
+
// LEVEL 3: XHR/Fetch Override
|
|
187
|
+
// ═══════════════════════════════════════════════════════════
|
|
188
|
+
|
|
189
|
+
${options.hookFetch ? `
|
|
190
|
+
// Hook fetch
|
|
191
|
+
const originalFetch = window.fetch;
|
|
192
|
+
window.fetch = async function(...args) {
|
|
193
|
+
const response = await originalFetch.apply(this, args);
|
|
194
|
+
try {
|
|
195
|
+
const clone = response.clone();
|
|
196
|
+
const text = await clone.text();
|
|
197
|
+
if (text && (text.includes('m3u8') || text.includes('.mpd') || text.includes('source'))) {
|
|
198
|
+
window.__uveCaptured.fetch.push({
|
|
199
|
+
url: args[0],
|
|
200
|
+
response: text.substring(0, 5000),
|
|
201
|
+
timestamp: Date.now()
|
|
202
|
+
});
|
|
203
|
+
// Extract URLs
|
|
204
|
+
const urls = text.match(/(https?:\\/\\/[^\\s"'<>]+\\.(m3u8|mpd|mp4)[^\\s"'<>]*)/gi);
|
|
205
|
+
if (urls) {
|
|
206
|
+
urls.forEach(u => window.__uveCaptured.fetch.push({ url: u, timestamp: Date.now() }));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} catch(e) {}
|
|
210
|
+
return response;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Hook XMLHttpRequest
|
|
214
|
+
const originalXHROpen = XMLHttpRequest.prototype.open;
|
|
215
|
+
const originalXHRSend = XMLHttpRequest.prototype.send;
|
|
216
|
+
|
|
217
|
+
XMLHttpRequest.prototype.open = function(method, url, ...rest) {
|
|
218
|
+
this._url = url;
|
|
219
|
+
return originalXHROpen.apply(this, [method, url, ...rest]);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
XMLHttpRequest.prototype.send = function(...args) {
|
|
223
|
+
this.addEventListener('load', function() {
|
|
224
|
+
try {
|
|
225
|
+
const text = this.responseText;
|
|
226
|
+
if (text && (text.includes('m3u8') || text.includes('.mpd'))) {
|
|
227
|
+
window.__uveCaptured.fetch.push({
|
|
228
|
+
url: this._url,
|
|
229
|
+
response: text.substring(0, 5000),
|
|
230
|
+
timestamp: Date.now()
|
|
231
|
+
});
|
|
232
|
+
const urls = text.match(/(https?:\\/\\/[^\\s"'<>]+\\.(m3u8|mpd|mp4)[^\\s"'<>]*)/gi);
|
|
233
|
+
if (urls) {
|
|
234
|
+
urls.forEach(u => window.__uveCaptured.fetch.push({ url: u, timestamp: Date.now() }));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} catch(e) {}
|
|
238
|
+
});
|
|
239
|
+
return originalXHRSend.apply(this, args);
|
|
240
|
+
};
|
|
241
|
+
` : ''}
|
|
242
|
+
|
|
243
|
+
// ═══════════════════════════════════════════════════════════
|
|
244
|
+
// LEVEL 4: Video Element Monitor
|
|
245
|
+
// ═══════════════════════════════════════════════════════════
|
|
246
|
+
|
|
247
|
+
// Monitor video.src changes
|
|
248
|
+
const videoObserver = new MutationObserver((mutations) => {
|
|
249
|
+
document.querySelectorAll('video').forEach(video => {
|
|
250
|
+
const src = video.src || video.currentSrc;
|
|
251
|
+
if (src && !src.startsWith('blob:')) {
|
|
252
|
+
window.__uveCaptured.videoSrc.push({
|
|
253
|
+
url: src,
|
|
254
|
+
timestamp: Date.now()
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
// Check source elements
|
|
258
|
+
video.querySelectorAll('source').forEach(source => {
|
|
259
|
+
if (source.src && !source.src.startsWith('blob:')) {
|
|
260
|
+
window.__uveCaptured.videoSrc.push({
|
|
261
|
+
url: source.src,
|
|
262
|
+
timestamp: Date.now()
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
videoObserver.observe(document.documentElement, {
|
|
270
|
+
childList: true,
|
|
271
|
+
subtree: true,
|
|
272
|
+
attributes: true,
|
|
273
|
+
attributeFilter: ['src']
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// ═══════════════════════════════════════════════════════════
|
|
277
|
+
// LEVEL 5: HLS.js/Dash.js Hooks
|
|
278
|
+
// ═══════════════════════════════════════════════════════════
|
|
279
|
+
|
|
280
|
+
${options.hookHls ? `
|
|
281
|
+
// Hook Hls.js
|
|
282
|
+
Object.defineProperty(window, 'Hls', {
|
|
283
|
+
configurable: true,
|
|
284
|
+
set(HlsClass) {
|
|
285
|
+
this._Hls = HlsClass;
|
|
286
|
+
if (HlsClass && HlsClass.prototype) {
|
|
287
|
+
const originalLoadSource = HlsClass.prototype.loadSource;
|
|
288
|
+
HlsClass.prototype.loadSource = function(url) {
|
|
289
|
+
window.__uveCaptured.hlsUrls.push({
|
|
290
|
+
url,
|
|
291
|
+
timestamp: Date.now(),
|
|
292
|
+
source: 'hls.js'
|
|
293
|
+
});
|
|
294
|
+
return originalLoadSource.call(this, url);
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
get() { return this._Hls; }
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Hook dashjs
|
|
302
|
+
Object.defineProperty(window, 'dashjs', {
|
|
303
|
+
configurable: true,
|
|
304
|
+
set(dashjsLib) {
|
|
305
|
+
this._dashjs = dashjsLib;
|
|
306
|
+
// Hook MediaPlayer if available
|
|
307
|
+
},
|
|
308
|
+
get() { return this._dashjs; }
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Hook common video player libraries
|
|
312
|
+
const hookPlayers = () => {
|
|
313
|
+
// JWPlayer
|
|
314
|
+
if (window.jwplayer) {
|
|
315
|
+
const players = document.querySelectorAll('[id^="jwplayer"]');
|
|
316
|
+
players.forEach(p => {
|
|
317
|
+
try {
|
|
318
|
+
const player = window.jwplayer(p.id);
|
|
319
|
+
const playlist = player.getPlaylist();
|
|
320
|
+
if (playlist) {
|
|
321
|
+
playlist.forEach(item => {
|
|
322
|
+
if (item.file) {
|
|
323
|
+
window.__uveCaptured.hlsUrls.push({
|
|
324
|
+
url: item.file,
|
|
325
|
+
timestamp: Date.now(),
|
|
326
|
+
source: 'jwplayer'
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
} catch(e) {}
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Video.js
|
|
336
|
+
if (window.videojs) {
|
|
337
|
+
document.querySelectorAll('.video-js').forEach(v => {
|
|
338
|
+
try {
|
|
339
|
+
const player = window.videojs(v.id);
|
|
340
|
+
const src = player.currentSrc();
|
|
341
|
+
if (src) {
|
|
342
|
+
window.__uveCaptured.hlsUrls.push({
|
|
343
|
+
url: src,
|
|
344
|
+
timestamp: Date.now(),
|
|
345
|
+
source: 'videojs'
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
} catch(e) {}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// Run hooks periodically
|
|
354
|
+
setInterval(hookPlayers, 2000);
|
|
355
|
+
` : ''}
|
|
356
|
+
|
|
357
|
+
})();
|
|
358
|
+
`);
|
|
359
|
+
// ═══════════════════════════════════════════════════════════════
|
|
360
|
+
// Smart Click Interactions
|
|
361
|
+
// ═══════════════════════════════════════════════════════════════
|
|
362
|
+
if (options.clickPlay) {
|
|
363
|
+
const maxInteractions = 3;
|
|
364
|
+
for (let i = 0; i < maxInteractions; i++) {
|
|
365
|
+
let clicked = false;
|
|
366
|
+
// Universal play/server button selectors
|
|
367
|
+
const playSelectors = [
|
|
368
|
+
'li[data-nume="1"]',
|
|
369
|
+
'.dooplay_player_option[data-nume="1"]',
|
|
370
|
+
'.server-item', '.server-btn',
|
|
371
|
+
'button[class*="play"]', '.play-button', '.play-btn',
|
|
372
|
+
'[aria-label*="Play"]', '[title*="Play"]',
|
|
373
|
+
'.jw-icon-playback', '.vjs-big-play-button',
|
|
374
|
+
'.plyr__control--overlaid', '[data-plyr="play"]',
|
|
375
|
+
'a[href*="download"]', '.download-btn',
|
|
376
|
+
'.get-link', '#get-link'
|
|
377
|
+
];
|
|
378
|
+
for (const selector of playSelectors) {
|
|
379
|
+
try {
|
|
380
|
+
const elements = await page.$$(selector);
|
|
381
|
+
for (const el of elements) {
|
|
382
|
+
const isVisible = await page.evaluate((e) => {
|
|
383
|
+
const style = window.getComputedStyle(e);
|
|
384
|
+
return style.display !== 'none' && style.visibility !== 'hidden';
|
|
385
|
+
}, el);
|
|
386
|
+
if (isVisible) {
|
|
387
|
+
try {
|
|
388
|
+
await el.evaluate((e) => e.scrollIntoView({ block: 'center' }));
|
|
389
|
+
await sleep(300);
|
|
390
|
+
await el.click().catch(() => page.evaluate((e) => e.click(), el));
|
|
391
|
+
await sleep(2000);
|
|
392
|
+
clicked = true;
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
catch (e) { /* ignore */ }
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (clicked)
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
catch (e) { /* ignore */ }
|
|
402
|
+
}
|
|
403
|
+
if (!clicked)
|
|
404
|
+
break;
|
|
405
|
+
await sleep(1000);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Wait for dynamic content
|
|
409
|
+
await sleep(options.waitTime);
|
|
410
|
+
// ═══════════════════════════════════════════════════════════════
|
|
411
|
+
// Collect All Captured Data
|
|
412
|
+
// ═══════════════════════════════════════════════════════════════
|
|
413
|
+
// Get captured data from page
|
|
414
|
+
const pageCaptures = await page.evaluate(() => {
|
|
415
|
+
return window.__uveCaptured || {
|
|
416
|
+
crypto: [],
|
|
417
|
+
fetch: [],
|
|
418
|
+
videoSrc: [],
|
|
419
|
+
hlsUrls: [],
|
|
420
|
+
dashUrls: []
|
|
421
|
+
};
|
|
422
|
+
});
|
|
423
|
+
// Remove network listeners
|
|
424
|
+
if (options.monitorNetwork) {
|
|
425
|
+
page.off('request', requestHandler);
|
|
426
|
+
page.off('response', responseHandler);
|
|
427
|
+
}
|
|
428
|
+
// ═══════════════════════════════════════════════════════════════
|
|
429
|
+
// Extract DOM Content
|
|
430
|
+
// ═══════════════════════════════════════════════════════════════
|
|
431
|
+
const domContent = await page.evaluate(({ types, includeEmbeds }) => {
|
|
432
|
+
const results = {
|
|
433
|
+
videos: [],
|
|
434
|
+
audio: [],
|
|
435
|
+
iframes: [],
|
|
436
|
+
downloadLinks: [],
|
|
437
|
+
platforms: []
|
|
438
|
+
};
|
|
439
|
+
// Videos
|
|
440
|
+
if (types.includes('video')) {
|
|
441
|
+
document.querySelectorAll('video').forEach((video, i) => {
|
|
442
|
+
results.videos.push({
|
|
443
|
+
index: i,
|
|
444
|
+
src: video.src || video.currentSrc,
|
|
445
|
+
poster: video.poster,
|
|
446
|
+
sources: Array.from(video.querySelectorAll('source')).map((s) => ({
|
|
447
|
+
src: s.src,
|
|
448
|
+
type: s.type
|
|
449
|
+
}))
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
// Audio
|
|
454
|
+
if (types.includes('audio')) {
|
|
455
|
+
document.querySelectorAll('audio').forEach((audio, i) => {
|
|
456
|
+
results.audio.push({
|
|
457
|
+
index: i,
|
|
458
|
+
src: audio.src || audio.currentSrc
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
// Iframes
|
|
463
|
+
if (types.includes('iframe')) {
|
|
464
|
+
document.querySelectorAll('iframe').forEach((iframe, i) => {
|
|
465
|
+
const src = iframe.src || iframe.getAttribute('data-src');
|
|
466
|
+
results.iframes.push({
|
|
467
|
+
index: i,
|
|
468
|
+
src,
|
|
469
|
+
platform: (src || '').includes('youtube') ? 'YouTube' :
|
|
470
|
+
(src || '').includes('vimeo') ? 'Vimeo' :
|
|
471
|
+
(src || '').includes('dailymotion') ? 'Dailymotion' : 'Unknown'
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
// Download links
|
|
476
|
+
document.querySelectorAll('a[href*="download"], a[href*=".mp4"], a[href*=".mkv"], .download-btn').forEach((el) => {
|
|
477
|
+
results.downloadLinks.push({
|
|
478
|
+
href: el.href,
|
|
479
|
+
text: el.textContent?.trim()
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
// Detect platforms from page content
|
|
483
|
+
const bodyText = document.body.innerHTML.toLowerCase();
|
|
484
|
+
const platformPatterns = ['streamtape', 'doodstream', 'filemoon', 'mixdrop', 'voe.sx', 'upns.online'];
|
|
485
|
+
platformPatterns.forEach(p => {
|
|
486
|
+
if (bodyText.includes(p))
|
|
487
|
+
results.platforms.push(p);
|
|
488
|
+
});
|
|
489
|
+
return results;
|
|
490
|
+
}, { types: options.types, includeEmbeds: options.includeEmbeds });
|
|
491
|
+
// ═══════════════════════════════════════════════════════════════
|
|
492
|
+
// Merge All Results
|
|
493
|
+
// ═══════════════════════════════════════════════════════════════
|
|
494
|
+
result.videos = domContent.videos;
|
|
495
|
+
result.audio = domContent.audio;
|
|
496
|
+
result.iframes = domContent.iframes;
|
|
497
|
+
result.downloadLinks = domContent.downloadLinks;
|
|
498
|
+
result.hostingPlatforms = domContent.platforms;
|
|
499
|
+
// Deduplicate and categorize captured URLs
|
|
500
|
+
const seenUrls = new Set();
|
|
501
|
+
// Network captures
|
|
502
|
+
networkCaptures.forEach(c => {
|
|
503
|
+
if (!seenUrls.has(c.url)) {
|
|
504
|
+
seenUrls.add(c.url);
|
|
505
|
+
if (c.url.includes('.m3u8')) {
|
|
506
|
+
result.m3u8Streams.push(c);
|
|
507
|
+
}
|
|
508
|
+
else if (c.url.includes('.mpd')) {
|
|
509
|
+
result.mpdStreams.push(c);
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
result.directUrls.push(c);
|
|
513
|
+
}
|
|
514
|
+
result.captureStats.networkCaptures++;
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
// Crypto captures
|
|
518
|
+
pageCaptures.crypto.forEach((c) => {
|
|
519
|
+
const url = c.url || c.decrypted;
|
|
520
|
+
if (url && !seenUrls.has(url)) {
|
|
521
|
+
seenUrls.add(url);
|
|
522
|
+
result.decryptedUrls.push({ url, type: 'crypto', timestamp: c.timestamp });
|
|
523
|
+
result.captureStats.cryptoCaptures++;
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
// Fetch captures
|
|
527
|
+
pageCaptures.fetch.forEach((c) => {
|
|
528
|
+
if (c.url && !seenUrls.has(c.url)) {
|
|
529
|
+
seenUrls.add(c.url);
|
|
530
|
+
if (c.url.includes('.m3u8')) {
|
|
531
|
+
result.m3u8Streams.push({ url: c.url, type: 'fetch', timestamp: c.timestamp });
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
result.directUrls.push({ url: c.url, type: 'fetch', timestamp: c.timestamp });
|
|
535
|
+
}
|
|
536
|
+
result.captureStats.fetchCaptures++;
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
// Video element captures
|
|
540
|
+
pageCaptures.videoSrc.forEach((c) => {
|
|
541
|
+
if (c.url && !seenUrls.has(c.url)) {
|
|
542
|
+
seenUrls.add(c.url);
|
|
543
|
+
if (c.url.includes('.m3u8')) {
|
|
544
|
+
result.m3u8Streams.push({ url: c.url, type: 'video_element', timestamp: c.timestamp });
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
result.directUrls.push({ url: c.url, type: 'video_element', timestamp: c.timestamp });
|
|
548
|
+
}
|
|
549
|
+
result.captureStats.videoElementCaptures++;
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
// HLS/Dash captures
|
|
553
|
+
[...pageCaptures.hlsUrls, ...pageCaptures.dashUrls].forEach((c) => {
|
|
554
|
+
if (c.url && !seenUrls.has(c.url)) {
|
|
555
|
+
seenUrls.add(c.url);
|
|
556
|
+
if (c.url.includes('.m3u8')) {
|
|
557
|
+
result.m3u8Streams.push({ url: c.url, type: 'hls_hook', timestamp: c.timestamp, metadata: { source: c.source } });
|
|
558
|
+
}
|
|
559
|
+
else if (c.url.includes('.mpd')) {
|
|
560
|
+
result.mpdStreams.push({ url: c.url, type: 'hls_hook', timestamp: c.timestamp });
|
|
561
|
+
}
|
|
562
|
+
result.captureStats.hlsHookCaptures++;
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
// ═══════════════════════════════════════════════════════════════
|
|
566
|
+
// Generate Summary
|
|
567
|
+
// ═══════════════════════════════════════════════════════════════
|
|
568
|
+
const totalUrls = result.m3u8Streams.length + result.mpdStreams.length +
|
|
569
|
+
result.directUrls.length + result.decryptedUrls.length;
|
|
570
|
+
const summary = `
|
|
571
|
+
🎬 Universal Video Extractor Results
|
|
572
|
+
════════════════════════════════════════
|
|
573
|
+
|
|
574
|
+
📊 Summary:
|
|
575
|
+
• M3U8 Streams: ${result.m3u8Streams.length}
|
|
576
|
+
• MPD Streams: ${result.mpdStreams.length}
|
|
577
|
+
• Direct URLs: ${result.directUrls.length}
|
|
578
|
+
• Decrypted URLs: ${result.decryptedUrls.length}
|
|
579
|
+
• Videos in DOM: ${result.videos.length}
|
|
580
|
+
• Iframes: ${result.iframes.length}
|
|
581
|
+
• Download Links: ${result.downloadLinks.length}
|
|
582
|
+
• Platforms Detected: ${result.hostingPlatforms.length}
|
|
583
|
+
|
|
584
|
+
📡 Capture Stats (5-Level System):
|
|
585
|
+
• Level 1 (Network): ${result.captureStats.networkCaptures}
|
|
586
|
+
• Level 2 (Crypto): ${result.captureStats.cryptoCaptures}
|
|
587
|
+
• Level 3 (Fetch): ${result.captureStats.fetchCaptures}
|
|
588
|
+
• Level 4 (Video Element): ${result.captureStats.videoElementCaptures}
|
|
589
|
+
• Level 5 (HLS/Dash Hooks): ${result.captureStats.hlsHookCaptures}
|
|
590
|
+
|
|
591
|
+
${result.m3u8Streams.length > 0 ? `
|
|
592
|
+
📺 M3U8 Streams:
|
|
593
|
+
${result.m3u8Streams.slice(0, 10).map((s, i) => ` ${i + 1}. ${s.url}\n [${s.type}]`).join('\n')}
|
|
594
|
+
${result.m3u8Streams.length > 10 ? ` ... and ${result.m3u8Streams.length - 10} more` : ''}
|
|
595
|
+
` : ''}
|
|
596
|
+
|
|
597
|
+
${result.decryptedUrls.length > 0 ? `
|
|
598
|
+
🔓 Decrypted URLs:
|
|
599
|
+
${result.decryptedUrls.slice(0, 5).map((s, i) => ` ${i + 1}. ${s.url}`).join('\n')}
|
|
600
|
+
` : ''}
|
|
601
|
+
|
|
602
|
+
${result.directUrls.length > 0 ? `
|
|
603
|
+
🎥 Direct Video URLs:
|
|
604
|
+
${result.directUrls.slice(0, 5).map((s, i) => ` ${i + 1}. ${s.url}\n [${s.type}]`).join('\n')}
|
|
605
|
+
` : ''}
|
|
606
|
+
|
|
607
|
+
${result.hostingPlatforms.length > 0 ? `
|
|
608
|
+
🌐 Detected Platforms: ${result.hostingPlatforms.join(', ')}
|
|
609
|
+
` : ''}
|
|
610
|
+
|
|
611
|
+
📋 Full Data (JSON):
|
|
612
|
+
${JSON.stringify(result, null, 2)}
|
|
613
|
+
`;
|
|
614
|
+
return {
|
|
615
|
+
content: [{
|
|
616
|
+
type: 'text',
|
|
617
|
+
text: summary
|
|
618
|
+
}]
|
|
619
|
+
};
|
|
620
|
+
}, 'Failed to extract video sources with universal extractor');
|
|
621
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -109,6 +109,8 @@ import { handleAjaxContentWaiter, } from "./handlers/dynamic-session-handlers.js
|
|
|
109
109
|
import { handleProgressTracker, } from "./handlers/monitoring-reporting-handlers.js";
|
|
110
110
|
// Import advanced extraction handlers (Ad-bypass & Obfuscation)
|
|
111
111
|
import { handleAdvancedVideoExtraction, handleMultiLayerRedirectTrace, handleAdProtectionDetector, } from "./handlers/advanced-extraction-handlers.js";
|
|
112
|
+
// Import universal video extractor
|
|
113
|
+
import { handleUniversalVideoExtractor } from "./handlers/universal-video-extractor.js";
|
|
112
114
|
// Initialize MCP server
|
|
113
115
|
const server = new Server(SERVER_INFO, { capabilities: CAPABILITIES });
|
|
114
116
|
// Register initialize handler (CRITICAL - missing handler can cause crash)
|
|
@@ -270,6 +272,9 @@ export async function executeToolByName(name, args) {
|
|
|
270
272
|
case TOOL_NAMES.AD_PROTECTION_DETECTOR:
|
|
271
273
|
result = await handleAdProtectionDetector(args || {});
|
|
272
274
|
break;
|
|
275
|
+
case TOOL_NAMES.UNIVERSAL_VIDEO_EXTRACTOR:
|
|
276
|
+
result = await handleUniversalVideoExtractor(args || {});
|
|
277
|
+
break;
|
|
273
278
|
default:
|
|
274
279
|
throw new Error(`Unknown tool: ${name}`);
|
|
275
280
|
}
|
package/dist/tool-definitions.js
CHANGED
|
@@ -479,6 +479,25 @@ export const TOOLS = [
|
|
|
479
479
|
},
|
|
480
480
|
},
|
|
481
481
|
},
|
|
482
|
+
{
|
|
483
|
+
name: 'universal_video_extractor',
|
|
484
|
+
description: 'Universal Video Extractor - 100% success rate video URL extraction. Combines media_extractor + advanced_video_extraction with 5-Level Capture System: Network Layer, Crypto Hooks, XHR/Fetch Override, Video Element Monitor, and HLS.js/Dash.js Hooks. Automatically captures AES-decrypted URLs, M3U8/MPD streams, and handles all obfuscation types.',
|
|
485
|
+
inputSchema: {
|
|
486
|
+
type: 'object',
|
|
487
|
+
properties: {
|
|
488
|
+
types: { type: 'array', items: { type: 'string' }, description: 'Media types to extract (video, audio, iframe, image). Empty for all.' },
|
|
489
|
+
includeEmbeds: { type: 'boolean', default: true, description: 'Include embedded content from iframes.' },
|
|
490
|
+
waitTime: { type: 'number', default: 15000, description: 'Time to wait for dynamic content (milliseconds).' },
|
|
491
|
+
clickPlay: { type: 'boolean', default: true, description: 'Auto-click play buttons to trigger video loading.' },
|
|
492
|
+
monitorNetwork: { type: 'boolean', default: true, description: 'Monitor network for video URLs.' },
|
|
493
|
+
hookCrypto: { type: 'boolean', default: true, description: 'Hook CryptoJS/crypto functions to capture decrypted URLs.' },
|
|
494
|
+
hookFetch: { type: 'boolean', default: true, description: 'Hook fetch/XHR to capture API responses.' },
|
|
495
|
+
hookHls: { type: 'boolean', default: true, description: 'Hook HLS.js/Dash.js/JWPlayer to capture stream URLs.' },
|
|
496
|
+
detectObfuscation: { type: 'boolean', default: true, description: 'Detect and report obfuscated JavaScript.' },
|
|
497
|
+
extractDownloads: { type: 'boolean', default: true, description: 'Extract download links from page.' },
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
},
|
|
482
501
|
];
|
|
483
502
|
// Tool name constants for type safety
|
|
484
503
|
export const TOOL_NAMES = {
|
|
@@ -535,6 +554,7 @@ export const TOOL_NAMES = {
|
|
|
535
554
|
ADVANCED_VIDEO_EXTRACTION: 'advanced_video_extraction',
|
|
536
555
|
MULTI_LAYER_REDIRECT_TRACE: 'multi_layer_redirect_trace',
|
|
537
556
|
AD_PROTECTION_DETECTOR: 'ad_protection_detector',
|
|
557
|
+
UNIVERSAL_VIDEO_EXTRACTOR: 'universal_video_extractor',
|
|
538
558
|
};
|
|
539
559
|
// Tool categories for organization
|
|
540
560
|
export const TOOL_CATEGORIES = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.18.0",
|
|
4
4
|
"description": "Universal AI IDE MCP Server - Auto-detects and supports all AI IDEs (Claude Desktop, Cursor, Windsurf, Cline, Zed, VSCode, Qoder AI, etc.) with Brave browser automation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|