brave-real-browser-mcp-server 2.9.20 → 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.
- package/dist/handlers/advanced-extraction-handlers.js +483 -0
- package/dist/handlers/advanced-video-media-handlers.js +100 -41
- package/dist/handlers/api-integration-handlers.js +2 -1
- package/dist/handlers/captcha-handlers.js +2 -1
- package/dist/handlers/dynamic-session-handlers.js +41 -9
- package/dist/handlers/pagination-handlers.js +5 -5
- package/dist/handlers/smart-data-extractors.js +4 -4
- package/dist/handlers/visual-tools-handlers.js +2 -1
- package/dist/index.js +231 -106
- package/dist/mcp-response-validator.js +145 -0
- package/dist/tool-definitions.js +50 -0
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
408
|
-
|
|
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
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
|
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
|
-
}
|
|
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
|