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.
- package/dist/browser-manager.js +7 -0
- package/dist/handlers/browser-handlers.js +1 -1
- package/dist/handlers/specialized-tools-handlers.js +464 -0
- package/dist/handlers/video-extraction-handlers.js +373 -0
- package/dist/index.js +29 -0
- package/dist/tool-definitions.js +582 -0
- package/dist/video-extraction/dom-extractors.js +421 -0
- package/dist/video-extraction/network-extractors.js +421 -0
- package/dist/video-extraction/types.js +61 -0
- package/package.json +1 -1
package/dist/browser-manager.js
CHANGED
|
@@ -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
|
+
};
|