brave-real-browser-mcp-server 2.17.1 โ 2.17.3
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/index.js +0 -12
- package/package.json +2 -2
- package/scripts/check-tool-registration.ts +0 -66
- package/scripts/full-verification.ts +0 -98
- package/scripts/live-verification.ts +0 -61
- package/scripts/setup-brave.cjs +0 -171
- package/scripts/verify-brave-installer.cjs +0 -13
- package/scripts/verify-fixes-custom.ts +0 -108
- package/scripts/verify-fixes-standalone.js +0 -244
- package/scripts/verify-fixes-standalone.ts +0 -248
- package/scripts/verify-ublock.ts +0 -58
package/dist/index.js
CHANGED
|
@@ -1,25 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Debug logging setup - Log process start
|
|
3
2
|
import 'dotenv/config';
|
|
4
|
-
console.error(`๐ [DEBUG] Process starting - PID: ${process.pid}, Node: ${process.version}, Platform: ${process.platform}`);
|
|
5
|
-
console.error(`๐ [DEBUG] Working directory: ${process.cwd()}`);
|
|
6
|
-
console.error(`๐ [DEBUG] Command args: ${process.argv.join(" ")}`);
|
|
7
3
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
5
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ListPromptsRequestSchema, InitializeRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
-
console.error("๐ [DEBUG] MCP SDK imports completed successfully");
|
|
11
|
-
// Import extracted modules
|
|
12
|
-
console.error("๐ [DEBUG] Loading tool definitions...");
|
|
13
6
|
import { TOOLS, SERVER_INFO, CAPABILITIES, TOOL_NAMES, } from "./tool-definitions.js";
|
|
14
|
-
console.error("๐ [DEBUG] Loading system utils...");
|
|
15
7
|
import { withErrorHandling } from "./system-utils.js";
|
|
16
8
|
import { validateMCPResponse } from "./mcp-response-validator.js";
|
|
17
|
-
console.error("๐ [DEBUG] Loading browser manager...");
|
|
18
9
|
import { closeBrowser, forceKillAllBraveProcesses } from "./browser-manager.js";
|
|
19
|
-
console.error("๐ [DEBUG] Loading core infrastructure...");
|
|
20
10
|
import { setupProcessCleanup, } from "./core-infrastructure.js";
|
|
21
11
|
// Import handlers
|
|
22
|
-
console.error("๐ [DEBUG] Loading handlers...");
|
|
23
12
|
import { handleBrowserInit, handleBrowserClose, } from "./handlers/browser-handlers.js";
|
|
24
13
|
import { handleNavigate, handleWait } from "./handlers/navigation-handlers.js";
|
|
25
14
|
import { handleClick, handleType, handleSolveCaptcha, handleRandomScroll, handlePressKey, } from "./handlers/interaction-handlers.js";
|
|
@@ -53,7 +42,6 @@ import { handleVideoPlayerFinder, handleStreamDetector, handleVideoDownloadLinkF
|
|
|
53
42
|
import { handleAdvancedVideoExtraction, handleDeobfuscateJS, handleMultiLayerRedirectTrace, handleAdProtectionDetector, } from "./handlers/advanced-extraction-handlers.js";
|
|
54
43
|
console.error("๐ [DEBUG] All modules loaded successfully");
|
|
55
44
|
console.error(`๐ [DEBUG] Server info: ${JSON.stringify(SERVER_INFO)}`);
|
|
56
|
-
console.error(`๐ [DEBUG] Available tools: ${TOOLS.length} tools loaded`);
|
|
57
45
|
// Initialize MCP server
|
|
58
46
|
console.error("๐ [DEBUG] Creating MCP server instance...");
|
|
59
47
|
const server = new Server(SERVER_INFO, { capabilities: CAPABILITIES });
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser-mcp-server",
|
|
3
|
-
"version": "2.17.
|
|
3
|
+
"version": "2.17.3",
|
|
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",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"preinstall": "npm view brave-real-puppeteer-core version > .latest-version 2>&1 || echo 'Version check skipped'",
|
|
9
|
-
"postinstall": "node scripts/patch-puppeteer-screen-recorder.cjs
|
|
9
|
+
"postinstall": "node scripts/patch-puppeteer-screen-recorder.cjs",
|
|
10
10
|
"clean": "rimraf dist",
|
|
11
11
|
"clean:cache": "npm cache clean --force",
|
|
12
12
|
"fix-cache-permissions": "echo 'Run: sudo chown -R $(whoami):$(id -gn) ~/.npm' && echo 'This fixes npm cache permission issues'",
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
|
|
5
|
-
// Simple regex parser since we can't easily import the typescript modules directly in a node script without compilation steps
|
|
6
|
-
function checkRegistration() {
|
|
7
|
-
const rootDir = path.resolve(__dirname, '..');
|
|
8
|
-
const toolDefsPath = path.join(rootDir, 'src', 'tool-definitions.ts');
|
|
9
|
-
const indexPath = path.join(rootDir, 'src', 'index.ts');
|
|
10
|
-
|
|
11
|
-
console.log(`Checking definitions in: ${toolDefsPath}`);
|
|
12
|
-
console.log(`Checking registration in: ${indexPath}`);
|
|
13
|
-
|
|
14
|
-
const toolDefsContent = fs.readFileSync(toolDefsPath, 'utf8');
|
|
15
|
-
const indexContent = fs.readFileSync(indexPath, 'utf8');
|
|
16
|
-
|
|
17
|
-
// Extract TOOL_NAMES constants
|
|
18
|
-
// Looks like: BROWSER_INIT: 'browser_init',
|
|
19
|
-
const toolNameRegex = /([A-Z_]+):\s*'([^']+)'/g;
|
|
20
|
-
const definedTools = new Map();
|
|
21
|
-
let match;
|
|
22
|
-
|
|
23
|
-
// We only want the ones inside "export const TOOL_NAMES = {" block
|
|
24
|
-
// So let's isolate that block roughly
|
|
25
|
-
const toolsBlockMatch = toolDefsContent.match(/export const TOOL_NAMES = \{([\s\S]*?)\}/);
|
|
26
|
-
if (!toolsBlockMatch) {
|
|
27
|
-
console.error("Could not find TOOL_NAMES block in definitions!");
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
const toolsBlock = toolsBlockMatch[1];
|
|
31
|
-
|
|
32
|
-
while ((match = toolNameRegex.exec(toolsBlock)) !== null) {
|
|
33
|
-
// match[1] is Key (BROWSER_INIT), match[2] is Value (browser_init)
|
|
34
|
-
const key = match[1];
|
|
35
|
-
const value = match[2];
|
|
36
|
-
definedTools.set(key, value);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
console.log(`Found ${definedTools.size} defined tools.`);
|
|
40
|
-
|
|
41
|
-
// Check index.ts for cases
|
|
42
|
-
// We look for `case TOOL_NAMES.KEY:` or `case "value":`
|
|
43
|
-
const missingTools = [];
|
|
44
|
-
|
|
45
|
-
for (const [key, value] of definedTools.entries()) {
|
|
46
|
-
const keyPattern = `case TOOL_NAMES.${key}`;
|
|
47
|
-
const valuePattern = `case "${value}"`;
|
|
48
|
-
const valuePatternSingle = `case '${value}'`;
|
|
49
|
-
|
|
50
|
-
if (!indexContent.includes(keyPattern) &&
|
|
51
|
-
!indexContent.includes(valuePattern) &&
|
|
52
|
-
!indexContent.includes(valuePatternSingle)) {
|
|
53
|
-
missingTools.push({ key, value });
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (missingTools.length > 0) {
|
|
58
|
-
console.error("โ The following tools are MISSING in index.ts:");
|
|
59
|
-
missingTools.forEach(t => console.error(` - ${t.key} (${t.value})`));
|
|
60
|
-
process.exit(1);
|
|
61
|
-
} else {
|
|
62
|
-
console.log("โ
All tools appear to be registered in index.ts!");
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
checkRegistration();
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { handleBrowserInit, handleBrowserClose } from '../src/handlers/browser-handlers.js';
|
|
3
|
-
import { handleNavigate, handleWait } from '../src/handlers/navigation-handlers.js';
|
|
4
|
-
import { handleGetContent, handleFindSelector } from '../src/handlers/content-handlers.js';
|
|
5
|
-
import { handleBreadcrumbNavigator } from '../src/handlers/navigation-handlers.js';
|
|
6
|
-
import {
|
|
7
|
-
handleBatchElementScraper,
|
|
8
|
-
handleAttributeHarvester,
|
|
9
|
-
handleLinkHarvester,
|
|
10
|
-
handleMediaExtractor
|
|
11
|
-
} from '../src/handlers/multi-element-handlers.js';
|
|
12
|
-
import {
|
|
13
|
-
handleKeywordSearch,
|
|
14
|
-
handleRegexPatternMatcher,
|
|
15
|
-
handleXPathSupport,
|
|
16
|
-
handleAdvancedCSSSelectors
|
|
17
|
-
} from '../src/handlers/search-filter-handlers.js';
|
|
18
|
-
import { handleRandomScroll } from '../src/handlers/interaction-handlers.js';
|
|
19
|
-
import { handleScrapeMetaTags, handleExtractSchema } from '../src/handlers/data-extraction-handlers.js';
|
|
20
|
-
|
|
21
|
-
async function runFullVerification() {
|
|
22
|
-
console.log('๐ Starting Comprehensive Tool Verification...');
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
await handleBrowserInit({ headless: true });
|
|
26
|
-
|
|
27
|
-
// Testing on one site primarily to save time, then brief check on second
|
|
28
|
-
const url = 'https://moviesdrive.forum/';
|
|
29
|
-
console.log(`\n--------------------------------------------------`);
|
|
30
|
-
console.log(`๐ Targeting: ${url}`);
|
|
31
|
-
console.log(`--------------------------------------------------`);
|
|
32
|
-
|
|
33
|
-
// --- Navigation & Basic ---
|
|
34
|
-
console.log(`\n[1/12] ๐ข Testing Navigation & Wait...`);
|
|
35
|
-
await handleNavigate({ url });
|
|
36
|
-
await handleWait({ type: 'timeout', value: '2000' });
|
|
37
|
-
console.log(' โ
Navigation complete.');
|
|
38
|
-
|
|
39
|
-
// --- Interaction ---
|
|
40
|
-
console.log(`\n[2/12] ๐ข Testing Random Scroll...`);
|
|
41
|
-
await handleRandomScroll({});
|
|
42
|
-
console.log(' โ
Scroll complete.');
|
|
43
|
-
|
|
44
|
-
// --- Content Handlers ---
|
|
45
|
-
console.log(`\n[3/12] ๐ข Testing Find Selector (Text search)...`);
|
|
46
|
-
const findRes = await handleFindSelector({ text: 'Movie' }); // Assuming "Movie" exists
|
|
47
|
-
console.log(` Result: Found ${findRes.content[0].text.length > 50 ? 'matches' : 'no matches'} (Length: ${findRes.content[0].text.length})`);
|
|
48
|
-
|
|
49
|
-
// --- Multi-Element Handlers (The file we kept) ---
|
|
50
|
-
console.log(`\n[4/12] ๐ข Testing Batch Element Scraper...`);
|
|
51
|
-
const batchRes = await handleBatchElementScraper({ selector: 'a', maxElements: 3 });
|
|
52
|
-
console.log(` Result: ${batchRes.content[0].text.substring(0, 100)}...`);
|
|
53
|
-
|
|
54
|
-
console.log(`\n[5/12] ๐ข Testing Attribute Harvester...`);
|
|
55
|
-
const attrRes = await handleAttributeHarvester({ selector: 'img', attributes: ['src'], maxElements: 3 });
|
|
56
|
-
console.log(` Result: ${attrRes.content[0].text.substring(0, 100)}...`);
|
|
57
|
-
|
|
58
|
-
console.log(`\n[6/12] ๐ข Testing Media Extractor...`); // Might be empty on home page but runs logic
|
|
59
|
-
const mediaRes = await handleMediaExtractor({ types: ['video', 'iframe'] });
|
|
60
|
-
console.log(` Result: ${mediaRes.content[0].text.substring(0, 100)}...`);
|
|
61
|
-
|
|
62
|
-
// --- Search & Filter Handlers (The file we kept) ---
|
|
63
|
-
console.log(`\n[7/12] ๐ข Testing Keyword Search...`);
|
|
64
|
-
const keyRes = await handleKeywordSearch({ keywords: ['Bollywood', 'Hollywood'] });
|
|
65
|
-
console.log(` Result: ${keyRes.content[0].text.substring(0, 100)}...`);
|
|
66
|
-
|
|
67
|
-
console.log(`\n[8/12] ๐ข Testing Regex Pattern Matcher...`);
|
|
68
|
-
const regexRes = await handleRegexPatternMatcher({ pattern: 'https?://[^\\s"\']+' });
|
|
69
|
-
console.log(` Result: ${regexRes.content[0].text.substring(0, 100)}...`);
|
|
70
|
-
|
|
71
|
-
console.log(`\n[9/12] ๐ข Testing XPath Support...`);
|
|
72
|
-
const xpathRes = await handleXPathSupport({ xpath: '//body//div' });
|
|
73
|
-
console.log(` Result: ${xpathRes.content[0].text.substring(0, 100)}...`);
|
|
74
|
-
|
|
75
|
-
console.log(`\n[10/12] ๐ข Testing Advanced CSS Selectors...`);
|
|
76
|
-
const cssRes = await handleAdvancedCSSSelectors({ selector: 'div > a', operation: 'query' });
|
|
77
|
-
console.log(` Result: ${cssRes.content[0].text.substring(0, 100)}...`);
|
|
78
|
-
|
|
79
|
-
// --- Data Extraction ---
|
|
80
|
-
console.log(`\n[11/12] ๐ข Testing Schema Extraction...`);
|
|
81
|
-
const schemaRes = await handleExtractSchema({});
|
|
82
|
-
console.log(` Result: ${schemaRes.content[0].text.substring(0, 100)}...`);
|
|
83
|
-
|
|
84
|
-
// --- Pagination (Refactored) ---
|
|
85
|
-
console.log(`\n[12/12] ๐ข Testing Breadcrumb Navigator...`);
|
|
86
|
-
const breadRes = await handleBreadcrumbNavigator({});
|
|
87
|
-
console.log(` Result: ${breadRes.content[0].text.substring(0, 100)}...`);
|
|
88
|
-
|
|
89
|
-
console.log('\nโ
All primary handler categories verified successfully.');
|
|
90
|
-
|
|
91
|
-
} catch (error) {
|
|
92
|
-
console.error('\nโ Verification Failed:', error);
|
|
93
|
-
} finally {
|
|
94
|
-
await handleBrowserClose({});
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
runFullVerification();
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { handleBrowserInit, handleBrowserClose } from '../src/handlers/browser-handlers.js';
|
|
3
|
-
import { handleNavigate } from '../src/handlers/navigation-handlers.js';
|
|
4
|
-
import { handleGetContent } from '../src/handlers/content-handlers.js';
|
|
5
|
-
import { handleBreadcrumbNavigator } from '../src/handlers/navigation-handlers.js';
|
|
6
|
-
import { handleLinkHarvester } from '../src/handlers/multi-element-handlers.js';
|
|
7
|
-
import { handleScrapeMetaTags } from '../src/handlers/data-extraction-handlers.js';
|
|
8
|
-
|
|
9
|
-
async function runVerification() {
|
|
10
|
-
console.log('๐ Starting Verification on Live Sites...');
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
// 1. Initialize Browser
|
|
14
|
-
console.log('\n๐ต Initializing Browser...');
|
|
15
|
-
await handleBrowserInit({ headless: true });
|
|
16
|
-
|
|
17
|
-
const sites = [
|
|
18
|
-
'https://moviesdrive.forum/',
|
|
19
|
-
'https://multimovies.golf/'
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
for (const url of sites) {
|
|
23
|
-
console.log(`\n--------------------------------------------------`);
|
|
24
|
-
console.log(`๐ Testing Site: ${url}`);
|
|
25
|
-
console.log(`--------------------------------------------------`);
|
|
26
|
-
|
|
27
|
-
// 2. Navigate
|
|
28
|
-
console.log(`\nโก๏ธ Navigating to ${url}...`);
|
|
29
|
-
await handleNavigate({ url });
|
|
30
|
-
|
|
31
|
-
// 3. Get Content (HTML preview)
|
|
32
|
-
console.log(`\n๐ Fetching Content (Preview)...`);
|
|
33
|
-
const contentRes = await handleGetContent({ type: 'text' });
|
|
34
|
-
console.log(` Result: ${contentRes.content[0].text.substring(0, 100)}...`);
|
|
35
|
-
|
|
36
|
-
// 4. Test Breadcrumb Navigator (Newly moved)
|
|
37
|
-
console.log(`\nnav Testing Breadcrumb Navigator...`);
|
|
38
|
-
const breadcrumbRes = await handleBreadcrumbNavigator({});
|
|
39
|
-
console.log(` Result: ${breadcrumbRes.content[0].text.substring(0, 200)}...`);
|
|
40
|
-
|
|
41
|
-
// 5. Test Link Harvester (Existing tool)
|
|
42
|
-
console.log(`\n๐ Testing Link Harvester (First 5 links)...`);
|
|
43
|
-
const linksRes = await handleLinkHarvester({ maxElements: 5 });
|
|
44
|
-
console.log(` Result: ${linksRes.content[0].text.substring(0, 200)}...`);
|
|
45
|
-
|
|
46
|
-
// 6. Test Meta Tags (Data extraction)
|
|
47
|
-
console.log(`\n๐ท๏ธ Testing Meta Tag Scraper...`);
|
|
48
|
-
const metaRes = await handleScrapeMetaTags({});
|
|
49
|
-
console.log(` Result: ${metaRes.content[0].text.substring(0, 200)}...`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
} catch (error) {
|
|
53
|
-
console.error('\nโ Verification Failed:', error);
|
|
54
|
-
} finally {
|
|
55
|
-
// 7. Cleanup
|
|
56
|
-
console.log('\n๐ด Closing Browser...');
|
|
57
|
-
await handleBrowserClose({});
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
runVerification();
|
package/scripts/setup-brave.cjs
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const { execSync, exec } = require('child_process');
|
|
5
|
-
const https = require('https');
|
|
6
|
-
|
|
7
|
-
// Colors for console output
|
|
8
|
-
const colors = {
|
|
9
|
-
reset: "\x1b[0m",
|
|
10
|
-
bright: "\x1b[1m",
|
|
11
|
-
green: "\x1b[32m",
|
|
12
|
-
yellow: "\x1b[33m",
|
|
13
|
-
red: "\x1b[31m",
|
|
14
|
-
cyan: "\x1b[36m"
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
function log(message, type = 'info') {
|
|
18
|
-
const timestamp = new Date().toISOString();
|
|
19
|
-
let color = colors.reset;
|
|
20
|
-
if (type === 'success') color = colors.green;
|
|
21
|
-
if (type === 'warn') color = colors.yellow;
|
|
22
|
-
if (type === 'error') color = colors.red;
|
|
23
|
-
if (type === 'info') color = colors.cyan;
|
|
24
|
-
console.log(`${color}[${type.toUpperCase()}] ${message}${colors.reset}`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const platform = os.platform();
|
|
28
|
-
|
|
29
|
-
// Define standard paths including the user's specific request
|
|
30
|
-
const bravePaths = {
|
|
31
|
-
win32: [
|
|
32
|
-
'C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe', // User's requested primary path
|
|
33
|
-
'C:\\Program Files (x86)\\BraveSoftware\\Brave-Browser\\Application\\brave.exe',
|
|
34
|
-
path.join(os.homedir(), 'AppData', 'Local', 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe')
|
|
35
|
-
],
|
|
36
|
-
darwin: [
|
|
37
|
-
'/Applications/Brave Browser.app/Contents/MacOS/Brave Browser'
|
|
38
|
-
],
|
|
39
|
-
linux: [
|
|
40
|
-
'/usr/bin/brave-browser',
|
|
41
|
-
'/usr/bin/brave',
|
|
42
|
-
'/snap/bin/brave'
|
|
43
|
-
]
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
async function checkBraveInstallation() {
|
|
47
|
-
log("Checking for Brave Browser installation...", 'info');
|
|
48
|
-
|
|
49
|
-
let foundPath = null;
|
|
50
|
-
const pathsToCheck = bravePaths[platform] || [];
|
|
51
|
-
|
|
52
|
-
for (const p of pathsToCheck) {
|
|
53
|
-
if (fs.existsSync(p)) {
|
|
54
|
-
foundPath = p;
|
|
55
|
-
break;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (foundPath) {
|
|
60
|
-
log(`Brave Browser found at: ${foundPath}`, 'success');
|
|
61
|
-
updateEnvFile(foundPath);
|
|
62
|
-
return true;
|
|
63
|
-
} else {
|
|
64
|
-
log("Brave Browser not found in standard locations.", 'warn');
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function updateEnvFile(bravePath) {
|
|
70
|
-
const envPath = path.join(__dirname, '..', '.env');
|
|
71
|
-
try {
|
|
72
|
-
let envContent = '';
|
|
73
|
-
if (fs.existsSync(envPath)) {
|
|
74
|
-
envContent = fs.readFileSync(envPath, 'utf8');
|
|
75
|
-
} else {
|
|
76
|
-
// Create from example if possible, or just start empty
|
|
77
|
-
const examplePath = path.join(__dirname, '..', '.env.example');
|
|
78
|
-
if(fs.existsSync(examplePath)) {
|
|
79
|
-
envContent = fs.readFileSync(examplePath, 'utf8');
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Check if BRAVE_PATH is already set
|
|
84
|
-
if (envContent.includes('BRAVE_PATH=')) {
|
|
85
|
-
// Replace existing
|
|
86
|
-
const newContent = envContent.replace(/^BRAVE_PATH=.*$/m, `BRAVE_PATH=${bravePath}`);
|
|
87
|
-
if (newContent !== envContent) {
|
|
88
|
-
fs.writeFileSync(envPath, newContent);
|
|
89
|
-
log("Updated .env with detected Brave path.", 'success');
|
|
90
|
-
} else {
|
|
91
|
-
log(".env already has a BRAVE_PATH set (or format differs).", 'info');
|
|
92
|
-
}
|
|
93
|
-
} else {
|
|
94
|
-
// Append
|
|
95
|
-
fs.appendFileSync(envPath, `\nBRAVE_PATH=${bravePath}\n`);
|
|
96
|
-
log("Appended BRAVE_PATH to .env.", 'success');
|
|
97
|
-
}
|
|
98
|
-
} catch (err) {
|
|
99
|
-
log(`Failed to update .env: ${err.message}`, 'error');
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function installBrave() {
|
|
104
|
-
log("Attempting to install Brave Browser...", 'info');
|
|
105
|
-
|
|
106
|
-
if (platform === 'win32') {
|
|
107
|
-
const installerUrl = 'https://laptop-updates.brave.com/latest/winx64'; // Standard stub installer
|
|
108
|
-
const installerPath = path.join(os.tmpdir(), 'BraveBrowserSetup.exe');
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
await downloadFile(installerUrl, installerPath);
|
|
112
|
-
log(`Installer downloaded to ${installerPath}`, 'success');
|
|
113
|
-
log("Running installer... Please follow the prompts if any appear.", 'info');
|
|
114
|
-
|
|
115
|
-
// Run installer
|
|
116
|
-
// /silent /install are common flags for Chromium based browsers.
|
|
117
|
-
// But we use start to let it run interactively if needed, or silent if possible.
|
|
118
|
-
// The standard stub installer usually just runs.
|
|
119
|
-
exec(`"${installerPath}"`, (error, stdout, stderr) => {
|
|
120
|
-
if (error) {
|
|
121
|
-
log(`Installer execution failed: ${error.message}`, 'error');
|
|
122
|
-
} else {
|
|
123
|
-
log("Installer started. Please complete the installation.", 'success');
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
} catch (err) {
|
|
128
|
-
log(`Failed to download/run installer: ${err.message}`, 'error');
|
|
129
|
-
log("Please install Brave manually from https://brave.com/download/", 'warn');
|
|
130
|
-
}
|
|
131
|
-
} else if (platform === 'darwin') {
|
|
132
|
-
log("Automatic installation on macOS is complex. Opening download page...", 'info');
|
|
133
|
-
exec('open https://brave.com/download/');
|
|
134
|
-
} else if (platform === 'linux') {
|
|
135
|
-
log("Please install Brave using your package manager (apt, dnf, snap, etc.).", 'info');
|
|
136
|
-
console.log("Visit: https://brave.com/linux/");
|
|
137
|
-
} else {
|
|
138
|
-
log(`Unsupported platform for auto-install: ${platform}`, 'error');
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function downloadFile(url, dest) {
|
|
143
|
-
return new Promise((resolve, reject) => {
|
|
144
|
-
const file = fs.createWriteStream(dest);
|
|
145
|
-
https.get(url, (response) => {
|
|
146
|
-
if (response.statusCode !== 200) {
|
|
147
|
-
reject(new Error(`Failed to download: Status Code ${response.statusCode}`));
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
response.pipe(file);
|
|
151
|
-
file.on('finish', () => {
|
|
152
|
-
file.close(resolve);
|
|
153
|
-
});
|
|
154
|
-
}).on('error', (err) => {
|
|
155
|
-
fs.unlink(dest, () => {}); // Delete the file async. (But we don't check result)
|
|
156
|
-
reject(err);
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async function main() {
|
|
162
|
-
const isInstalled = await checkBraveInstallation();
|
|
163
|
-
if (!isInstalled) {
|
|
164
|
-
await installBrave();
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
main().catch(err => {
|
|
169
|
-
console.error("Setup script failed:", err);
|
|
170
|
-
process.exit(0);
|
|
171
|
-
});
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
const { BraveInstaller } = require('../dist/brave-installer.js');
|
|
3
|
-
|
|
4
|
-
console.log('Checking BraveInstaller module...');
|
|
5
|
-
|
|
6
|
-
if (typeof BraveInstaller.install === 'function') {
|
|
7
|
-
console.log('โ
PASS: BraveInstaller.install is a function');
|
|
8
|
-
} else {
|
|
9
|
-
console.error('โ FAIL: BraveInstaller.install is NOT a function');
|
|
10
|
-
process.exit(1);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
console.log('Module structure verified.');
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { chromium } from 'playwright';
|
|
3
|
-
// Mock interfaces to simulate MCP handler context if needed, but for now we'll just run them as standalone if possible,
|
|
4
|
-
// OR simpler: we'll just simulate the logic using playwright directly to prove the logic works,
|
|
5
|
-
// OR better: Import the handlers and run them.
|
|
6
|
-
// Since handlers depend on 'browser-manager' which has global state, we need to initialize it.
|
|
7
|
-
|
|
8
|
-
import { handleLinkHarvester } from '../src/handlers/multi-element-handlers.js';
|
|
9
|
-
import { handleApiFinder } from '../src/handlers/smart-data-extractors.js';
|
|
10
|
-
import { handleExtractJSON } from '../src/handlers/data-extraction-handlers.js';
|
|
11
|
-
import { handleFetchXHR } from '../src/handlers/smart-data-extractors.js'; // Ensure this export exists
|
|
12
|
-
import { setBrowser, setPage } from '../src/browser-manager.js';
|
|
13
|
-
|
|
14
|
-
async function verify() {
|
|
15
|
-
console.log('๐ Starting Verification...');
|
|
16
|
-
const browser = await chromium.launch({ headless: true });
|
|
17
|
-
const context = await browser.newContext();
|
|
18
|
-
const page = await context.newPage();
|
|
19
|
-
|
|
20
|
-
// Set global state for handlers
|
|
21
|
-
setBrowser(browser as any);
|
|
22
|
-
setPage(page as any);
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
// 1. Test Link Harvester (with Buttons)
|
|
26
|
-
console.log('\n๐งช Testing Link Harvester...');
|
|
27
|
-
await page.setContent(`
|
|
28
|
-
<html>
|
|
29
|
-
<body>
|
|
30
|
-
<a href="https://example.com">Example</a>
|
|
31
|
-
<button>Click me</button>
|
|
32
|
-
<a href="#anchor">Anchor</a>
|
|
33
|
-
</body>
|
|
34
|
-
</html>
|
|
35
|
-
`);
|
|
36
|
-
// This should NOT throw now
|
|
37
|
-
const links = await handleLinkHarvester({ selector: 'button, a', classifyLinks: true });
|
|
38
|
-
console.log('โ
Link Harvester Result (should have 2 links, no crash):', links);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// 2. Test API Finder
|
|
42
|
-
console.log('\n๐งช Testing API Finder...');
|
|
43
|
-
// Note: API finder listens for responses, so we need to trigger some or have them present
|
|
44
|
-
await page.setContent(`
|
|
45
|
-
<html>
|
|
46
|
-
<script>
|
|
47
|
-
const apiUrl = "https://api.example.com/v1/users";
|
|
48
|
-
</script>
|
|
49
|
-
</html>
|
|
50
|
-
`);
|
|
51
|
-
const apis = await handleApiFinder({ duration: 1000 });
|
|
52
|
-
console.log('โ
API Finder Result:', apis);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// 3. Test Extract JSON
|
|
56
|
-
console.log('\n๐งช Testing Extract JSON...');
|
|
57
|
-
await page.setContent(`
|
|
58
|
-
<html>
|
|
59
|
-
<script>
|
|
60
|
-
const config = { "siteId": 123, "name": "Test Site" };
|
|
61
|
-
const simple = [1, 2, 3];
|
|
62
|
-
</script>
|
|
63
|
-
<script type="application/json">
|
|
64
|
-
{ "valid": "json" }
|
|
65
|
-
</script>
|
|
66
|
-
</html>
|
|
67
|
-
`);
|
|
68
|
-
const json = await handleExtractJSON({ source: 'all' });
|
|
69
|
-
// Should find the application/json AND the embedded config object
|
|
70
|
-
console.log('โ
Extract JSON Result:', JSON.stringify(json, null, 2));
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// 4. Test Fetch XHR (Force Reload)
|
|
74
|
-
console.log('\n๐งช Testing Fetch XHR...');
|
|
75
|
-
// We need a real page for this to effectively test reload, or mock network
|
|
76
|
-
// Let's use a simple public page
|
|
77
|
-
console.log('Navigating to example.com to test network capture...');
|
|
78
|
-
// Mock network requests for reliability
|
|
79
|
-
await page.route('**/api/data', route => route.fulfill({
|
|
80
|
-
status: 200,
|
|
81
|
-
contentType: 'application/json',
|
|
82
|
-
body: JSON.stringify({ success: true })
|
|
83
|
-
}));
|
|
84
|
-
|
|
85
|
-
// We will manually trigger a fetch on load
|
|
86
|
-
await page.goto('https://example.com');
|
|
87
|
-
await page.evaluate(() => {
|
|
88
|
-
fetch('/api/data');
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const requests = await handleFetchXHR({ duration: 2000, forceReload: true });
|
|
92
|
-
// Note: forceReload might clear the manual fetch above if it reloads immediately.
|
|
93
|
-
// Actually fetch_xhr with forceReload causes a reload. So we rely on the page itself making requests.
|
|
94
|
-
// example.com is static.
|
|
95
|
-
// Let's rely on our manual fetch being captured if we DON'T reload, or rely on reload to capture real page assets.
|
|
96
|
-
// For this test, let's turn forceReload OFF to verify we capture the manual fetch,
|
|
97
|
-
// AND run another test with forceReload ON to verify it doesn't crash (even if example.com has no XHR).
|
|
98
|
-
|
|
99
|
-
console.log('โ
Fetch XHR Result:', requests);
|
|
100
|
-
|
|
101
|
-
} catch (error) {
|
|
102
|
-
console.error('โ Verification Failed:', error);
|
|
103
|
-
} finally {
|
|
104
|
-
await browser.close();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
verify();
|
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { chromium } from 'playwright';
|
|
3
|
-
|
|
4
|
-
// --- MOCK / INLINED HANDLERS ---
|
|
5
|
-
|
|
6
|
-
// 1. Link Harvester (Fixed)
|
|
7
|
-
async function handleLinkHarvester(page, args) {
|
|
8
|
-
const selector = args.selector || 'a[href]';
|
|
9
|
-
const classifyLinks = args.classifyLinks !== false;
|
|
10
|
-
const includeAnchors = args.includeAnchors || false;
|
|
11
|
-
|
|
12
|
-
const linkData = await page.evaluate(
|
|
13
|
-
({ selector, classifyLinks, includeAnchors }) => {
|
|
14
|
-
const links = document.querySelectorAll(selector);
|
|
15
|
-
const currentDomain = window.location.hostname;
|
|
16
|
-
const results = [];
|
|
17
|
-
|
|
18
|
-
links.forEach((link, index) => {
|
|
19
|
-
const href = link.href;
|
|
20
|
-
|
|
21
|
-
// Fixed: Check for href
|
|
22
|
-
if (!href) return;
|
|
23
|
-
|
|
24
|
-
// Skip anchors if not included
|
|
25
|
-
if (!includeAnchors && href.startsWith('#')) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const linkInfo = {
|
|
30
|
-
index,
|
|
31
|
-
href,
|
|
32
|
-
text: link.textContent?.trim() || '',
|
|
33
|
-
title: link.title || '',
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
if (classifyLinks) {
|
|
37
|
-
try {
|
|
38
|
-
const url = new URL(href, window.location.href);
|
|
39
|
-
const isInternal = url.hostname === currentDomain;
|
|
40
|
-
const isAnchor = href.startsWith('#');
|
|
41
|
-
const isMailto = href.startsWith('mailto:');
|
|
42
|
-
const isTel = href.startsWith('tel:');
|
|
43
|
-
|
|
44
|
-
linkInfo.type = isAnchor
|
|
45
|
-
? 'anchor'
|
|
46
|
-
: isMailto
|
|
47
|
-
? 'email'
|
|
48
|
-
: isTel
|
|
49
|
-
? 'phone'
|
|
50
|
-
: isInternal
|
|
51
|
-
? 'internal'
|
|
52
|
-
: 'external';
|
|
53
|
-
|
|
54
|
-
linkInfo.domain = url.hostname;
|
|
55
|
-
linkInfo.protocol = url.protocol;
|
|
56
|
-
} catch (e) {
|
|
57
|
-
linkInfo.type = 'invalid';
|
|
58
|
-
linkInfo.domain = 'unknown';
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
results.push(linkInfo);
|
|
62
|
-
});
|
|
63
|
-
return results;
|
|
64
|
-
},
|
|
65
|
-
{ selector, classifyLinks, includeAnchors }
|
|
66
|
-
);
|
|
67
|
-
return linkData;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// 2. Extract JSON (Fixed)
|
|
71
|
-
async function handleExtractJSON(page, args) {
|
|
72
|
-
const source = args.source || 'all';
|
|
73
|
-
const selector = args.selector;
|
|
74
|
-
const filter = args.filter;
|
|
75
|
-
|
|
76
|
-
const jsonData = await page.evaluate(
|
|
77
|
-
({ source, selector, filter }) => {
|
|
78
|
-
const results = [];
|
|
79
|
-
|
|
80
|
-
// Extract JSON from script tags
|
|
81
|
-
if (source === 'script' || source === 'all') {
|
|
82
|
-
const defaultSelector = selector || 'script[type="application/json"], script[type="application/ld+json"], script';
|
|
83
|
-
const scripts = document.querySelectorAll(defaultSelector);
|
|
84
|
-
|
|
85
|
-
scripts.forEach((script, index) => {
|
|
86
|
-
const content = script.textContent || '';
|
|
87
|
-
try {
|
|
88
|
-
// 1. Try direct parsing first
|
|
89
|
-
const data = JSON.parse(content);
|
|
90
|
-
if (filter) {
|
|
91
|
-
const filterLower = filter.toLowerCase();
|
|
92
|
-
const dataStr = JSON.stringify(data).toLowerCase();
|
|
93
|
-
if (!dataStr.includes(filterLower)) return;
|
|
94
|
-
}
|
|
95
|
-
results.push({ data, source: 'script', path: `script[${index}]` });
|
|
96
|
-
} catch (e) {
|
|
97
|
-
// 2. Fallback: Try to find JSON objects using regex
|
|
98
|
-
const jsonRegex = /({[\s\S]*?}|\[[\s\S]*?\])/g;
|
|
99
|
-
let match;
|
|
100
|
-
while ((match = jsonRegex.exec(content)) !== null) {
|
|
101
|
-
const potentialJson = match[0];
|
|
102
|
-
if (potentialJson.length < 20) continue;
|
|
103
|
-
try {
|
|
104
|
-
const data = JSON.parse(potentialJson);
|
|
105
|
-
if (filter) {
|
|
106
|
-
const filterLower = filter.toLowerCase();
|
|
107
|
-
const dataStr = JSON.stringify(data).toLowerCase();
|
|
108
|
-
if (!dataStr.includes(filterLower)) continue;
|
|
109
|
-
}
|
|
110
|
-
if ((Array.isArray(data) && data.length > 0) || (typeof data === 'object' && data !== null && Object.keys(data).length > 0)) {
|
|
111
|
-
results.push({ data, source: 'script', path: `script[${index}]_regex_match` });
|
|
112
|
-
}
|
|
113
|
-
} catch (e2) { }
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
return results;
|
|
119
|
-
},
|
|
120
|
-
{ source, selector, filter }
|
|
121
|
-
);
|
|
122
|
-
return jsonData;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// 3. API Finder
|
|
126
|
-
async function handleApiFinder(page, args) {
|
|
127
|
-
const captureDuration = typeof args.duration === 'number' ? args.duration : 1000;
|
|
128
|
-
|
|
129
|
-
// From inline scripts
|
|
130
|
-
const scriptApis = await page.evaluate(() => {
|
|
131
|
-
const results = [];
|
|
132
|
-
const scripts = Array.from(document.querySelectorAll('script'));
|
|
133
|
-
|
|
134
|
-
const apiPatterns = [
|
|
135
|
-
/https?:\/\/[^"'\s]+\/api\/[^"'\s]*/gi,
|
|
136
|
-
/https?:\/\/api\.[^"'\s]+/gi,
|
|
137
|
-
];
|
|
138
|
-
|
|
139
|
-
scripts.forEach(script => {
|
|
140
|
-
const content = script.textContent || '';
|
|
141
|
-
apiPatterns.forEach(pattern => {
|
|
142
|
-
const matches = content.match(pattern);
|
|
143
|
-
if (matches) {
|
|
144
|
-
matches.forEach(match => results.push({ url: match, source: 'script' }));
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
return results;
|
|
149
|
-
});
|
|
150
|
-
return scriptApis;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// 4. Fetch XHR (Fixed)
|
|
154
|
-
async function handleFetchXHR(page, args) {
|
|
155
|
-
const duration = args.duration || 2000;
|
|
156
|
-
const forceReload = args.forceReload !== false;
|
|
157
|
-
|
|
158
|
-
const xhrData = [];
|
|
159
|
-
const responseHandler = async (response) => {
|
|
160
|
-
const request = response.request();
|
|
161
|
-
const resourceType = request.resourceType();
|
|
162
|
-
|
|
163
|
-
if (resourceType === 'xhr' || resourceType === 'fetch') {
|
|
164
|
-
try {
|
|
165
|
-
const body = await response.text();
|
|
166
|
-
xhrData.push({
|
|
167
|
-
url: response.url(),
|
|
168
|
-
body: body.substring(0, 1000),
|
|
169
|
-
});
|
|
170
|
-
} catch (e) { }
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
page.on('response', responseHandler);
|
|
175
|
-
|
|
176
|
-
if (forceReload) {
|
|
177
|
-
try {
|
|
178
|
-
await page.reload({ waitUntil: 'networkidle' });
|
|
179
|
-
} catch (e) { }
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Wait a bit
|
|
183
|
-
await new Promise(r => setTimeout(r, duration));
|
|
184
|
-
page.off('response', responseHandler);
|
|
185
|
-
return xhrData;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
async function verify() {
|
|
190
|
-
console.log('๐ Starting Verification...');
|
|
191
|
-
const browser = await chromium.launch({ headless: true });
|
|
192
|
-
const context = await browser.newContext();
|
|
193
|
-
const page = await context.newPage();
|
|
194
|
-
|
|
195
|
-
try {
|
|
196
|
-
// 1. Test Link Harvester
|
|
197
|
-
console.log('\n๐งช Testing Link Harvester...');
|
|
198
|
-
await page.setContent(`
|
|
199
|
-
<html><body>
|
|
200
|
-
<a href="https://example.com">Example</a>
|
|
201
|
-
<button>Click me</button>
|
|
202
|
-
<a href="#anchor">Anchor</a>
|
|
203
|
-
</body></html>
|
|
204
|
-
`);
|
|
205
|
-
const links = await handleLinkHarvester(page, { selector: 'button, a', classifyLinks: true });
|
|
206
|
-
console.log('โ
Link Harvester Result (should have 2 links):', links.length);
|
|
207
|
-
console.log(links);
|
|
208
|
-
|
|
209
|
-
// 2. Test Extract JSON
|
|
210
|
-
console.log('\n๐งช Testing Extract JSON...');
|
|
211
|
-
await page.setContent(`
|
|
212
|
-
<html>
|
|
213
|
-
<script>
|
|
214
|
-
const config = { "siteId": 123, "name": "Test Site" };
|
|
215
|
-
</script>
|
|
216
|
-
</html>
|
|
217
|
-
`);
|
|
218
|
-
const json = await handleExtractJSON(page, { source: 'all' });
|
|
219
|
-
const found = json.find(j => j.data.siteId === 123);
|
|
220
|
-
console.log('โ
Extract JSON Result:', found ? 'Found embedded JSON' : 'FAILED to find embedded JSON');
|
|
221
|
-
console.log(json);
|
|
222
|
-
|
|
223
|
-
// 3. Test API Finder
|
|
224
|
-
console.log('\n๐งช Testing API Finder...');
|
|
225
|
-
await page.setContent(`<html><script>const apiUrl = "https://api.example.com/v1/users";</script></html>`);
|
|
226
|
-
const apis = await handleApiFinder(page, {});
|
|
227
|
-
console.log('โ
API Finder Result:', apis.length > 0 ? 'Found API' : 'FAILED');
|
|
228
|
-
console.log(apis);
|
|
229
|
-
|
|
230
|
-
// 4. Test Fetch XHR
|
|
231
|
-
console.log('\n๐งช Testing Fetch XHR...');
|
|
232
|
-
await page.goto('https://example.com');
|
|
233
|
-
const xhr = await handleFetchXHR(page, { duration: 1000, forceReload: true });
|
|
234
|
-
console.log('โ
Fetch XHR executed without error. Captured:', xhr.length);
|
|
235
|
-
|
|
236
|
-
} catch (error) {
|
|
237
|
-
console.error('โ Verification Failed:', error);
|
|
238
|
-
process.exit(1);
|
|
239
|
-
} finally {
|
|
240
|
-
await browser.close();
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
verify();
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { chromium } from 'playwright';
|
|
3
|
-
|
|
4
|
-
// --- MOCK / INLINED HANDLERS ---
|
|
5
|
-
// Copied and adapted from source to verify logic without import issues
|
|
6
|
-
|
|
7
|
-
// 1. Link Harvester (Fixed)
|
|
8
|
-
async function handleLinkHarvester(page: any, args: any) {
|
|
9
|
-
const selector = args.selector || 'a[href]';
|
|
10
|
-
const classifyLinks = args.classifyLinks !== false;
|
|
11
|
-
const includeAnchors = args.includeAnchors || false;
|
|
12
|
-
|
|
13
|
-
const linkData = await page.evaluate(
|
|
14
|
-
({ selector, classifyLinks, includeAnchors }: any) => {
|
|
15
|
-
const links = document.querySelectorAll(selector);
|
|
16
|
-
const currentDomain = window.location.hostname;
|
|
17
|
-
const results: any[] = [];
|
|
18
|
-
|
|
19
|
-
links.forEach((link: any, index: number) => {
|
|
20
|
-
const href = (link as any).href;
|
|
21
|
-
|
|
22
|
-
// Fixed: Check for href
|
|
23
|
-
if (!href) return;
|
|
24
|
-
|
|
25
|
-
// Skip anchors if not included
|
|
26
|
-
if (!includeAnchors && href.startsWith('#')) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const linkInfo: any = {
|
|
31
|
-
index,
|
|
32
|
-
href,
|
|
33
|
-
text: link.textContent?.trim() || '',
|
|
34
|
-
title: link.title || '',
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
if (classifyLinks) {
|
|
38
|
-
try {
|
|
39
|
-
const url = new URL(href, window.location.href);
|
|
40
|
-
const isInternal = url.hostname === currentDomain;
|
|
41
|
-
const isAnchor = href.startsWith('#');
|
|
42
|
-
const isMailto = href.startsWith('mailto:');
|
|
43
|
-
const isTel = href.startsWith('tel:');
|
|
44
|
-
|
|
45
|
-
linkInfo.type = isAnchor
|
|
46
|
-
? 'anchor'
|
|
47
|
-
: isMailto
|
|
48
|
-
? 'email'
|
|
49
|
-
: isTel
|
|
50
|
-
? 'phone'
|
|
51
|
-
: isInternal
|
|
52
|
-
? 'internal'
|
|
53
|
-
: 'external';
|
|
54
|
-
|
|
55
|
-
linkInfo.domain = url.hostname;
|
|
56
|
-
linkInfo.protocol = url.protocol;
|
|
57
|
-
} catch (e) {
|
|
58
|
-
linkInfo.type = 'invalid';
|
|
59
|
-
linkInfo.domain = 'unknown';
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
results.push(linkInfo);
|
|
63
|
-
});
|
|
64
|
-
return results;
|
|
65
|
-
},
|
|
66
|
-
{ selector, classifyLinks, includeAnchors }
|
|
67
|
-
);
|
|
68
|
-
return linkData;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// 2. Extract JSON (Fixed)
|
|
72
|
-
async function handleExtractJSON(page: any, args: any) {
|
|
73
|
-
const source = args.source || 'all';
|
|
74
|
-
const selector = args.selector;
|
|
75
|
-
const filter = args.filter;
|
|
76
|
-
|
|
77
|
-
const jsonData = await page.evaluate(
|
|
78
|
-
({ source, selector, filter }: any) => {
|
|
79
|
-
const results: any[] = [];
|
|
80
|
-
|
|
81
|
-
// Extract JSON from script tags
|
|
82
|
-
if (source === 'script' || source === 'all') {
|
|
83
|
-
const defaultSelector = selector || 'script[type="application/json"], script[type="application/ld+json"], script';
|
|
84
|
-
const scripts = document.querySelectorAll(defaultSelector);
|
|
85
|
-
|
|
86
|
-
scripts.forEach((script: any, index: number) => {
|
|
87
|
-
const content = script.textContent || '';
|
|
88
|
-
try {
|
|
89
|
-
// 1. Try direct parsing first
|
|
90
|
-
const data = JSON.parse(content);
|
|
91
|
-
if (filter) {
|
|
92
|
-
const filterLower = filter.toLowerCase();
|
|
93
|
-
const dataStr = JSON.stringify(data).toLowerCase();
|
|
94
|
-
if (!dataStr.includes(filterLower)) return;
|
|
95
|
-
}
|
|
96
|
-
results.push({ data, source: 'script', path: `script[${index}]` });
|
|
97
|
-
} catch (e) {
|
|
98
|
-
// 2. Fallback: Try to find JSON objects using regex
|
|
99
|
-
const jsonRegex = /({[\s\S]*?}|\[[\s\S]*?\])/g;
|
|
100
|
-
let match;
|
|
101
|
-
while ((match = jsonRegex.exec(content)) !== null) {
|
|
102
|
-
const potentialJson = match[0];
|
|
103
|
-
if (potentialJson.length < 20) continue;
|
|
104
|
-
try {
|
|
105
|
-
const data = JSON.parse(potentialJson);
|
|
106
|
-
if (filter) {
|
|
107
|
-
const filterLower = filter.toLowerCase();
|
|
108
|
-
const dataStr = JSON.stringify(data).toLowerCase();
|
|
109
|
-
if (!dataStr.includes(filterLower)) continue;
|
|
110
|
-
}
|
|
111
|
-
if ((Array.isArray(data) && data.length > 0) || (typeof data === 'object' && data !== null && Object.keys(data).length > 0)) {
|
|
112
|
-
results.push({ data, source: 'script', path: `script[${index}]_regex_match` });
|
|
113
|
-
}
|
|
114
|
-
} catch (e2) { }
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
return results;
|
|
120
|
-
},
|
|
121
|
-
{ source, selector, filter }
|
|
122
|
-
);
|
|
123
|
-
return jsonData;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// 3. API Finder (Just verification of logic, no changes other than registration)
|
|
127
|
-
// We verify it works as intended.
|
|
128
|
-
async function handleApiFinder(page: any, args: any) {
|
|
129
|
-
const captureDuration = typeof args.duration === 'number' ? args.duration : 1000;
|
|
130
|
-
|
|
131
|
-
// From inline scripts
|
|
132
|
-
const scriptApis = await page.evaluate(() => {
|
|
133
|
-
const results: any[] = [];
|
|
134
|
-
const scripts = Array.from(document.querySelectorAll('script'));
|
|
135
|
-
|
|
136
|
-
const apiPatterns = [
|
|
137
|
-
/https?:\/\/[^"'\s]+\/api\/[^"'\s]*/gi,
|
|
138
|
-
/https?:\/\/api\.[^"'\s]+/gi,
|
|
139
|
-
];
|
|
140
|
-
|
|
141
|
-
scripts.forEach(script => {
|
|
142
|
-
const content = script.textContent || '';
|
|
143
|
-
apiPatterns.forEach(pattern => {
|
|
144
|
-
const matches = content.match(pattern);
|
|
145
|
-
if (matches) {
|
|
146
|
-
matches.forEach(match => results.push({ url: match, source: 'script' }));
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
return results;
|
|
151
|
-
});
|
|
152
|
-
return scriptApis;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// 4. Fetch XHR (Fixed)
|
|
156
|
-
async function handleFetchXHR(page: any, args: any) {
|
|
157
|
-
const duration = args.duration || 2000;
|
|
158
|
-
const forceReload = args.forceReload !== false;
|
|
159
|
-
|
|
160
|
-
const xhrData: any[] = [];
|
|
161
|
-
const responseHandler = async (response: any) => {
|
|
162
|
-
const request = response.request();
|
|
163
|
-
const resourceType = request.resourceType();
|
|
164
|
-
|
|
165
|
-
if (resourceType === 'xhr' || resourceType === 'fetch') {
|
|
166
|
-
try {
|
|
167
|
-
const body = await response.text();
|
|
168
|
-
xhrData.push({
|
|
169
|
-
url: response.url(),
|
|
170
|
-
body: body.substring(0, 1000),
|
|
171
|
-
});
|
|
172
|
-
} catch (e) { }
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
page.on('response', responseHandler);
|
|
177
|
-
|
|
178
|
-
if (forceReload) {
|
|
179
|
-
try {
|
|
180
|
-
await page.reload({ waitUntil: 'networkidle' }); // Playwright 'networkidle'
|
|
181
|
-
} catch (e) { }
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Wait a bit
|
|
185
|
-
await new Promise(r => setTimeout(r, duration));
|
|
186
|
-
page.off('response', responseHandler);
|
|
187
|
-
return xhrData;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
async function verify() {
|
|
192
|
-
console.log('๐ Starting Verification...');
|
|
193
|
-
const browser = await chromium.launch({ headless: true });
|
|
194
|
-
const context = await browser.newContext();
|
|
195
|
-
const page = await context.newPage();
|
|
196
|
-
|
|
197
|
-
try {
|
|
198
|
-
// 1. Test Link Harvester
|
|
199
|
-
console.log('\n๐งช Testing Link Harvester...');
|
|
200
|
-
await page.setContent(`
|
|
201
|
-
<html><body>
|
|
202
|
-
<a href="https://example.com">Example</a>
|
|
203
|
-
<button>Click me</button>
|
|
204
|
-
<a href="#anchor">Anchor</a>
|
|
205
|
-
</body></html>
|
|
206
|
-
`);
|
|
207
|
-
const links = await handleLinkHarvester(page, { selector: 'button, a', classifyLinks: true });
|
|
208
|
-
console.log('โ
Link Harvester Result (should have 2 links):', links.length);
|
|
209
|
-
console.log(links);
|
|
210
|
-
|
|
211
|
-
// 2. Test Extract JSON
|
|
212
|
-
console.log('\n๐งช Testing Extract JSON...');
|
|
213
|
-
await page.setContent(`
|
|
214
|
-
<html>
|
|
215
|
-
<script>
|
|
216
|
-
const config = { "siteId": 123, "name": "Test Site" };
|
|
217
|
-
</script>
|
|
218
|
-
</html>
|
|
219
|
-
`);
|
|
220
|
-
const json = await handleExtractJSON(page, { source: 'all' });
|
|
221
|
-
const found = json.find((j: any) => j.data.siteId === 123);
|
|
222
|
-
console.log('โ
Extract JSON Result:', found ? 'Found embedded JSON' : 'FAILED to find embedded JSON');
|
|
223
|
-
console.log(json);
|
|
224
|
-
|
|
225
|
-
// 3. Test API Finder
|
|
226
|
-
console.log('\n๐งช Testing API Finder...');
|
|
227
|
-
await page.setContent(`<html><script>const apiUrl = "https://api.example.com/v1/users";</script></html>`);
|
|
228
|
-
const apis = await handleApiFinder(page, {});
|
|
229
|
-
console.log('โ
API Finder Result:', apis.length > 0 ? 'Found API' : 'FAILED');
|
|
230
|
-
console.log(apis);
|
|
231
|
-
|
|
232
|
-
// 4. Test Fetch XHR
|
|
233
|
-
console.log('\n๐งช Testing Fetch XHR...');
|
|
234
|
-
// We can't easily mock responses in this simple script without extensive setup,
|
|
235
|
-
// but we can check if it attempts reload without error.
|
|
236
|
-
await page.goto('https://example.com');
|
|
237
|
-
const xhr = await handleFetchXHR(page, { duration: 1000, forceReload: true });
|
|
238
|
-
console.log('โ
Fetch XHR executed without error. Captured:', xhr.length);
|
|
239
|
-
|
|
240
|
-
} catch (error) {
|
|
241
|
-
console.error('โ Verification Failed:', error);
|
|
242
|
-
process.exit(1);
|
|
243
|
-
} finally {
|
|
244
|
-
await browser.close();
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
verify();
|
package/scripts/verify-ublock.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { ExtensionManager } from '../src/extension-manager.js';
|
|
3
|
-
import { initializeBrowser, closeBrowser } from '../src/browser-manager.js';
|
|
4
|
-
import * as fs from 'fs';
|
|
5
|
-
import * as path from 'path';
|
|
6
|
-
|
|
7
|
-
async function verifyUBlock() {
|
|
8
|
-
console.log('๐ Starting uBlock Origin Verification...');
|
|
9
|
-
|
|
10
|
-
// 1. Verify Download & Extraction
|
|
11
|
-
console.log('\nSTEP 1: Checking ExtensionManager...');
|
|
12
|
-
const extPath = await ExtensionManager.ensureUBlockOrigin();
|
|
13
|
-
console.log(`๐ Extension Path: ${extPath}`);
|
|
14
|
-
|
|
15
|
-
if (!extPath || !fs.existsSync(extPath)) {
|
|
16
|
-
console.error('โ Failed: Extension path does not exist!');
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const manifestPath = path.join(extPath, 'manifest.json');
|
|
21
|
-
if (fs.existsSync(manifestPath)) {
|
|
22
|
-
console.log('โ
Manifest.json found! Extension extracted correctly.');
|
|
23
|
-
} else {
|
|
24
|
-
console.error('โ Failed: manifest.json missing in extension folder!');
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// 2. Verify Browser Launch with Extension
|
|
29
|
-
console.log('\nSTEP 2: Launching Browser with uBlock...');
|
|
30
|
-
try {
|
|
31
|
-
const { browser } = await initializeBrowser({
|
|
32
|
-
headless: false, // Must be false or 'new' for extensions usually, but let's test our default config
|
|
33
|
-
autoInstall: true
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// Check if arguments were passed
|
|
37
|
-
const spawnArgs = browser.process()?.spawnargs || [];
|
|
38
|
-
const loadArg = spawnArgs.find(arg => arg.includes('--load-extension'));
|
|
39
|
-
|
|
40
|
-
if (loadArg && loadArg.includes(extPath)) {
|
|
41
|
-
console.log('โ
Browser launched with --load-extension argument pointing to uBlock!');
|
|
42
|
-
} else {
|
|
43
|
-
console.error('โ Failed: Browser launched but --load-extension arg missing or incorrect.');
|
|
44
|
-
console.error('Spawn Args:', spawnArgs);
|
|
45
|
-
await closeBrowser();
|
|
46
|
-
process.exit(1);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
await closeBrowser();
|
|
50
|
-
console.log('\nโจ VERIFICATION SUCCESSFUL: uBlock Origin is integrated!');
|
|
51
|
-
|
|
52
|
-
} catch (error) {
|
|
53
|
-
console.error('โ Browser Launch Failed:', error);
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
verifyUBlock();
|