brave-real-browser 2.0.7 → 2.0.9
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/lib/cjs/index.js +5 -0
- package/lib/cjs/module/pageController.js +0 -252
- package/lib/esm/index.mjs +5 -0
- package/lib/esm/module/pageController.mjs +0 -249
- package/package.json +4 -2
- package/test/cjs/test.js +32 -6
- package/test/esm/test.js +32 -6
- package/data/sdo.gif +0 -0
package/lib/cjs/index.js
CHANGED
|
@@ -29,6 +29,11 @@ async function connect({
|
|
|
29
29
|
} else {
|
|
30
30
|
// Use DEFAULT_FLAGS from brave-real-launcher
|
|
31
31
|
const flags = [...DEFAULT_FLAGS];
|
|
32
|
+
// Add AutomationControlled to "disable-features" flag to improve ReCaptcha V3 score
|
|
33
|
+
const indexDisableFeatures = flags.findIndex((flag) => flag.startsWith('--disable-features'));
|
|
34
|
+
if (indexDisableFeatures !== -1) {
|
|
35
|
+
flags[indexDisableFeatures] = `${flags[indexDisableFeatures]},AutomationControlled`;
|
|
36
|
+
}
|
|
32
37
|
braveFlags = [
|
|
33
38
|
...flags,
|
|
34
39
|
...args,
|
|
@@ -2,35 +2,10 @@ const { createCursor } = require('ghost-cursor');
|
|
|
2
2
|
const { checkTurnstile } = require('./turnstile.js');
|
|
3
3
|
const kill = require('tree-kill');
|
|
4
4
|
|
|
5
|
-
function getRandomInt(min, max) {
|
|
6
|
-
min = Math.ceil(min);
|
|
7
|
-
max = Math.floor(max);
|
|
8
|
-
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
5
|
async function pageController({ browser, page, proxy, turnstile, xvfbsession, pid, plugins, killProcess = false, brave }) {
|
|
12
6
|
|
|
13
7
|
let solveStatus = turnstile
|
|
14
8
|
|
|
15
|
-
// 🛡️ Import stealth scripts properly (Dynamic Import for compatibility)
|
|
16
|
-
let injectStealth = () => { };
|
|
17
|
-
try {
|
|
18
|
-
const stealthModule = require('brave-real-puppeteer-core/scripts/stealth-injector.js');
|
|
19
|
-
// Combine essential stealth scripts
|
|
20
|
-
const ultrafast = stealthModule.injectUltraFastPerformance();
|
|
21
|
-
const navStealth = stealthModule.injectNavigatorStealth();
|
|
22
|
-
injectStealth = async (page) => {
|
|
23
|
-
await page.evaluateOnNewDocument(ultrafast);
|
|
24
|
-
await page.evaluateOnNewDocument(navStealth);
|
|
25
|
-
};
|
|
26
|
-
} catch (e) {
|
|
27
|
-
// Fallback if module not found (shouldn't happen in monorepo)
|
|
28
|
-
console.error('Stealth module not found, using minimal fallback', e);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Apply stealth BEFORE any other page actions
|
|
32
|
-
if (page) await injectStealth(page);
|
|
33
|
-
|
|
34
9
|
page.on('close', () => {
|
|
35
10
|
solveStatus = false
|
|
36
11
|
});
|
|
@@ -63,233 +38,6 @@ async function pageController({ browser, page, proxy, turnstile, xvfbsession, pi
|
|
|
63
38
|
}
|
|
64
39
|
}
|
|
65
40
|
|
|
66
|
-
// 🛡️ COMPREHENSIVE STEALTH INJECTION - For Brotector/Datadome bypass
|
|
67
|
-
await page.evaluateOnNewDocument(() => {
|
|
68
|
-
// ============ NAVIGATOR WEBDRIVER ELIMINATION ============
|
|
69
|
-
Object.defineProperty(navigator, 'webdriver', {
|
|
70
|
-
get: () => undefined,
|
|
71
|
-
configurable: true
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// Delete webdriver from Object.getOwnPropertyDescriptor
|
|
75
|
-
const originalGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
76
|
-
Object.getOwnPropertyDescriptor = function (obj, prop) {
|
|
77
|
-
if (obj === navigator && prop === 'webdriver') return undefined;
|
|
78
|
-
return originalGetOwnPropertyDescriptor.apply(this, arguments);
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
// ============ PLUGINS & MIME TYPES ============
|
|
82
|
-
Object.defineProperty(navigator, 'plugins', {
|
|
83
|
-
get: () => {
|
|
84
|
-
const plugins = [
|
|
85
|
-
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
|
|
86
|
-
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
|
|
87
|
-
{ name: 'Native Client', filename: 'internal-nacl-plugin', description: '' }
|
|
88
|
-
];
|
|
89
|
-
plugins.length = 3;
|
|
90
|
-
plugins.item = i => plugins[i];
|
|
91
|
-
plugins.namedItem = n => plugins.find(p => p.name === n);
|
|
92
|
-
plugins.refresh = () => { };
|
|
93
|
-
return plugins;
|
|
94
|
-
},
|
|
95
|
-
configurable: true
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
Object.defineProperty(navigator, 'mimeTypes', {
|
|
99
|
-
get: () => {
|
|
100
|
-
const mimeTypes = [
|
|
101
|
-
{ type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format' },
|
|
102
|
-
{ type: 'text/pdf', suffixes: 'pdf', description: '' }
|
|
103
|
-
];
|
|
104
|
-
mimeTypes.length = 2;
|
|
105
|
-
mimeTypes.item = i => mimeTypes[i];
|
|
106
|
-
mimeTypes.namedItem = n => mimeTypes.find(m => m.type === n);
|
|
107
|
-
return mimeTypes;
|
|
108
|
-
},
|
|
109
|
-
configurable: true
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// ============ LANGUAGES ============
|
|
113
|
-
Object.defineProperty(navigator, 'languages', {
|
|
114
|
-
get: () => ['en-US', 'en'],
|
|
115
|
-
configurable: true
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// ============ CHROME RUNTIME REMOVAL ============
|
|
119
|
-
if (window.chrome) {
|
|
120
|
-
const originalChrome = window.chrome;
|
|
121
|
-
window.chrome = new Proxy(originalChrome, {
|
|
122
|
-
get(target, prop) {
|
|
123
|
-
if (prop === 'runtime') return undefined;
|
|
124
|
-
if (prop === 'csi') return undefined;
|
|
125
|
-
if (prop === 'loadTimes') return undefined;
|
|
126
|
-
return Reflect.get(target, prop);
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ============ PERMISSIONS API ============
|
|
132
|
-
if (navigator.permissions) {
|
|
133
|
-
const originalQuery = navigator.permissions.query.bind(navigator.permissions);
|
|
134
|
-
navigator.permissions.query = (params) => {
|
|
135
|
-
if (params.name === 'notifications') {
|
|
136
|
-
return Promise.resolve({ state: 'prompt', onchange: null });
|
|
137
|
-
}
|
|
138
|
-
return originalQuery(params);
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// ============ AUTOMATION SIGNATURES REMOVAL ============
|
|
143
|
-
const automationProps = [
|
|
144
|
-
'__puppeteer__', 'puppeteer', '__playwright__', 'playwright',
|
|
145
|
-
'__selenium_unwrapped', '__selenium_evaluate', '__webdriver_evaluate',
|
|
146
|
-
'__driver_evaluate', '_phantom', '__nightmare', 'callPhantom',
|
|
147
|
-
'__webdriver_script_fn', '__webdriver_script_func', 'cdc_adoQpoasnfa76pfcZLmcfl_Array',
|
|
148
|
-
'cdc_adoQpoasnfa76pfcZLmcfl_Promise', 'cdc_adoQpoasnfa76pfcZLmcfl_Symbol'
|
|
149
|
-
];
|
|
150
|
-
automationProps.forEach(prop => {
|
|
151
|
-
try { delete window[prop]; } catch (e) { }
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// ============ ERROR STACK SANITIZATION ============
|
|
155
|
-
const originalError = Error;
|
|
156
|
-
window.Error = function (...args) {
|
|
157
|
-
const error = new originalError(...args);
|
|
158
|
-
if (error.stack) {
|
|
159
|
-
error.stack = error.stack.split('\\n')
|
|
160
|
-
.filter(line => !line.includes('puppeteer') && !line.includes('playwright') && !line.includes('UtilityScript'))
|
|
161
|
-
.join('\\n');
|
|
162
|
-
}
|
|
163
|
-
return error;
|
|
164
|
-
};
|
|
165
|
-
window.Error.prototype = originalError.prototype;
|
|
166
|
-
|
|
167
|
-
// ============ HARDWARE SPOOFING ============
|
|
168
|
-
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 4 });
|
|
169
|
-
Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
|
|
170
|
-
|
|
171
|
-
// ============ CONNECTION API ============
|
|
172
|
-
if (navigator.connection) {
|
|
173
|
-
Object.defineProperty(navigator.connection, 'rtt', { get: () => 50 });
|
|
174
|
-
Object.defineProperty(navigator.connection, 'downlink', { get: () => 10 });
|
|
175
|
-
Object.defineProperty(navigator.connection, 'effectiveType', { get: () => '4g' });
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// MouseEvent screenX/screenY fix
|
|
180
|
-
// 🛡️ COMPREHENSIVE STEALTH INJECTION - For Brotector/Datadome bypass
|
|
181
|
-
await page.evaluateOnNewDocument(() => {
|
|
182
|
-
// ============ NAVIGATOR WEBDRIVER ELIMINATION ============
|
|
183
|
-
Object.defineProperty(navigator, 'webdriver', {
|
|
184
|
-
get: () => undefined,
|
|
185
|
-
configurable: true
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// Delete webdriver from Object.getOwnPropertyDescriptor
|
|
189
|
-
const originalGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
190
|
-
Object.getOwnPropertyDescriptor = function (obj, prop) {
|
|
191
|
-
if (obj === navigator && prop === 'webdriver') return undefined;
|
|
192
|
-
return originalGetOwnPropertyDescriptor.apply(this, arguments);
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
// ============ PLUGINS & MIME TYPES ============
|
|
196
|
-
Object.defineProperty(navigator, 'plugins', {
|
|
197
|
-
get: () => {
|
|
198
|
-
const plugins = [
|
|
199
|
-
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
|
|
200
|
-
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
|
|
201
|
-
{ name: 'Native Client', filename: 'internal-nacl-plugin', description: '' }
|
|
202
|
-
];
|
|
203
|
-
plugins.length = 3;
|
|
204
|
-
plugins.item = i => plugins[i];
|
|
205
|
-
plugins.namedItem = n => plugins.find(p => p.name === n);
|
|
206
|
-
plugins.refresh = () => { };
|
|
207
|
-
return plugins;
|
|
208
|
-
},
|
|
209
|
-
configurable: true
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
Object.defineProperty(navigator, 'mimeTypes', {
|
|
213
|
-
get: () => {
|
|
214
|
-
const mimeTypes = [
|
|
215
|
-
{ type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format' },
|
|
216
|
-
{ type: 'text/pdf', suffixes: 'pdf', description: '' }
|
|
217
|
-
];
|
|
218
|
-
mimeTypes.length = 2;
|
|
219
|
-
mimeTypes.item = i => mimeTypes[i];
|
|
220
|
-
mimeTypes.namedItem = n => mimeTypes.find(m => m.type === n);
|
|
221
|
-
return mimeTypes;
|
|
222
|
-
},
|
|
223
|
-
configurable: true
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// ============ LANGUAGES ============
|
|
227
|
-
Object.defineProperty(navigator, 'languages', {
|
|
228
|
-
get: () => ['en-US', 'en'],
|
|
229
|
-
configurable: true
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
// ============ CHROME RUNTIME REMOVAL ============
|
|
233
|
-
if (window.chrome) {
|
|
234
|
-
const originalChrome = window.chrome;
|
|
235
|
-
window.chrome = new Proxy(originalChrome, {
|
|
236
|
-
get(target, prop) {
|
|
237
|
-
if (prop === 'runtime') return undefined;
|
|
238
|
-
if (prop === 'csi') return undefined;
|
|
239
|
-
if (prop === 'loadTimes') return undefined;
|
|
240
|
-
return Reflect.get(target, prop);
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// ============ PERMISSIONS API ============
|
|
246
|
-
if (navigator.permissions) {
|
|
247
|
-
const originalQuery = navigator.permissions.query.bind(navigator.permissions);
|
|
248
|
-
navigator.permissions.query = (params) => {
|
|
249
|
-
if (params.name === 'notifications') {
|
|
250
|
-
return Promise.resolve({ state: 'prompt', onchange: null });
|
|
251
|
-
}
|
|
252
|
-
return originalQuery(params);
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// ============ AUTOMATION SIGNATURES REMOVAL ============
|
|
257
|
-
const automationProps = [
|
|
258
|
-
'__puppeteer__', 'puppeteer', '__playwright__', 'playwright',
|
|
259
|
-
'__selenium_unwrapped', '__selenium_evaluate', '__webdriver_evaluate',
|
|
260
|
-
'__driver_evaluate', '_phantom', '__nightmare', 'callPhantom',
|
|
261
|
-
'__webdriver_script_fn', '__webdriver_script_func', 'cdc_adoQpoasnfa76pfcZLmcfl_Array',
|
|
262
|
-
'cdc_adoQpoasnfa76pfcZLmcfl_Promise', 'cdc_adoQpoasnfa76pfcZLmcfl_Symbol'
|
|
263
|
-
];
|
|
264
|
-
automationProps.forEach(prop => {
|
|
265
|
-
try { delete window[prop]; } catch (e) { }
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// ============ ERROR STACK SANITIZATION ============
|
|
269
|
-
const originalError = Error;
|
|
270
|
-
window.Error = function (...args) {
|
|
271
|
-
const error = new originalError(...args);
|
|
272
|
-
if (error.stack) {
|
|
273
|
-
error.stack = error.stack.split('\\n')
|
|
274
|
-
.filter(line => !line.includes('puppeteer') && !line.includes('playwright') && !line.includes('UtilityScript'))
|
|
275
|
-
.join('\\n');
|
|
276
|
-
}
|
|
277
|
-
return error;
|
|
278
|
-
};
|
|
279
|
-
window.Error.prototype = originalError.prototype;
|
|
280
|
-
|
|
281
|
-
// ============ HARDWARE SPOOFING ============
|
|
282
|
-
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 4 });
|
|
283
|
-
Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
|
|
284
|
-
|
|
285
|
-
// ============ CONNECTION API ============
|
|
286
|
-
if (navigator.connection) {
|
|
287
|
-
Object.defineProperty(navigator.connection, 'rtt', { get: () => 50 });
|
|
288
|
-
Object.defineProperty(navigator.connection, 'downlink', { get: () => 10 });
|
|
289
|
-
Object.defineProperty(navigator.connection, 'effectiveType', { get: () => '4g' });
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
|
|
293
41
|
await page.evaluateOnNewDocument(() => {
|
|
294
42
|
Object.defineProperty(MouseEvent.prototype, 'screenX', {
|
|
295
43
|
get: function () {
|
package/lib/esm/index.mjs
CHANGED
|
@@ -28,6 +28,11 @@ export async function connect({
|
|
|
28
28
|
} else {
|
|
29
29
|
// Use DEFAULT_FLAGS from brave-real-launcher
|
|
30
30
|
const flags = [...DEFAULT_FLAGS];
|
|
31
|
+
// Add AutomationControlled to "disable-features" flag to improve ReCaptcha V3 score
|
|
32
|
+
const indexDisableFeatures = flags.findIndex((flag) => flag.startsWith('--disable-features'));
|
|
33
|
+
if (indexDisableFeatures !== -1) {
|
|
34
|
+
flags[indexDisableFeatures] = `${flags[indexDisableFeatures]},AutomationControlled`;
|
|
35
|
+
}
|
|
31
36
|
braveFlags = [
|
|
32
37
|
...flags,
|
|
33
38
|
...args,
|
|
@@ -2,32 +2,10 @@ import { createCursor } from 'ghost-cursor';
|
|
|
2
2
|
import { checkTurnstile } from './turnstile.mjs';
|
|
3
3
|
import kill from 'tree-kill';
|
|
4
4
|
|
|
5
|
-
function getRandomInt(min, max) {
|
|
6
|
-
min = Math.ceil(min);
|
|
7
|
-
max = Math.floor(max);
|
|
8
|
-
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
5
|
export async function pageController({ browser, page, proxy, turnstile, xvfbsession, pid, plugins, killProcess = false, brave }) {
|
|
12
6
|
|
|
13
7
|
let solveStatus = turnstile
|
|
14
8
|
|
|
15
|
-
// 🛡️ Import stealth scripts properly (Dynamic Import for compatibility)
|
|
16
|
-
let injectStealth = () => { };
|
|
17
|
-
try {
|
|
18
|
-
// Use dynamic import for ESM
|
|
19
|
-
const { injectUltraFastPerformance, injectNavigatorStealth } = await import('brave-real-puppeteer-core/scripts/stealth-injector.js');
|
|
20
|
-
injectStealth = async (page) => {
|
|
21
|
-
await page.evaluateOnNewDocument(injectUltraFastPerformance());
|
|
22
|
-
await page.evaluateOnNewDocument(injectNavigatorStealth());
|
|
23
|
-
};
|
|
24
|
-
} catch (e) {
|
|
25
|
-
console.error('Stealth module not found, using minimal fallback', e);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Apply stealth BEFORE any other page actions
|
|
29
|
-
if (page) await injectStealth(page);
|
|
30
|
-
|
|
31
9
|
page.on('close', () => {
|
|
32
10
|
solveStatus = false
|
|
33
11
|
});
|
|
@@ -60,233 +38,6 @@ export async function pageController({ browser, page, proxy, turnstile, xvfbsess
|
|
|
60
38
|
}
|
|
61
39
|
}
|
|
62
40
|
|
|
63
|
-
// 🛡️ COMPREHENSIVE STEALTH INJECTION - For Brotector/Datadome bypass
|
|
64
|
-
await page.evaluateOnNewDocument(() => {
|
|
65
|
-
// ============ NAVIGATOR WEBDRIVER ELIMINATION ============
|
|
66
|
-
Object.defineProperty(navigator, 'webdriver', {
|
|
67
|
-
get: () => undefined,
|
|
68
|
-
configurable: true
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// Delete webdriver from Object.getOwnPropertyDescriptor
|
|
72
|
-
const originalGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
73
|
-
Object.getOwnPropertyDescriptor = function (obj, prop) {
|
|
74
|
-
if (obj === navigator && prop === 'webdriver') return undefined;
|
|
75
|
-
return originalGetOwnPropertyDescriptor.apply(this, arguments);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// ============ PLUGINS & MIME TYPES ============
|
|
79
|
-
Object.defineProperty(navigator, 'plugins', {
|
|
80
|
-
get: () => {
|
|
81
|
-
const plugins = [
|
|
82
|
-
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
|
|
83
|
-
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
|
|
84
|
-
{ name: 'Native Client', filename: 'internal-nacl-plugin', description: '' }
|
|
85
|
-
];
|
|
86
|
-
plugins.length = 3;
|
|
87
|
-
plugins.item = i => plugins[i];
|
|
88
|
-
plugins.namedItem = n => plugins.find(p => p.name === n);
|
|
89
|
-
plugins.refresh = () => { };
|
|
90
|
-
return plugins;
|
|
91
|
-
},
|
|
92
|
-
configurable: true
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
Object.defineProperty(navigator, 'mimeTypes', {
|
|
96
|
-
get: () => {
|
|
97
|
-
const mimeTypes = [
|
|
98
|
-
{ type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format' },
|
|
99
|
-
{ type: 'text/pdf', suffixes: 'pdf', description: '' }
|
|
100
|
-
];
|
|
101
|
-
mimeTypes.length = 2;
|
|
102
|
-
mimeTypes.item = i => mimeTypes[i];
|
|
103
|
-
mimeTypes.namedItem = n => mimeTypes.find(m => m.type === n);
|
|
104
|
-
return mimeTypes;
|
|
105
|
-
},
|
|
106
|
-
configurable: true
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// ============ LANGUAGES ============
|
|
110
|
-
Object.defineProperty(navigator, 'languages', {
|
|
111
|
-
get: () => ['en-US', 'en'],
|
|
112
|
-
configurable: true
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// ============ CHROME RUNTIME REMOVAL ============
|
|
116
|
-
if (window.chrome) {
|
|
117
|
-
const originalChrome = window.chrome;
|
|
118
|
-
window.chrome = new Proxy(originalChrome, {
|
|
119
|
-
get(target, prop) {
|
|
120
|
-
if (prop === 'runtime') return undefined;
|
|
121
|
-
if (prop === 'csi') return undefined;
|
|
122
|
-
if (prop === 'loadTimes') return undefined;
|
|
123
|
-
return Reflect.get(target, prop);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// ============ PERMISSIONS API ============
|
|
129
|
-
if (navigator.permissions) {
|
|
130
|
-
const originalQuery = navigator.permissions.query.bind(navigator.permissions);
|
|
131
|
-
navigator.permissions.query = (params) => {
|
|
132
|
-
if (params.name === 'notifications') {
|
|
133
|
-
return Promise.resolve({ state: 'prompt', onchange: null });
|
|
134
|
-
}
|
|
135
|
-
return originalQuery(params);
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// ============ AUTOMATION SIGNATURES REMOVAL ============
|
|
140
|
-
const automationProps = [
|
|
141
|
-
'__puppeteer__', 'puppeteer', '__playwright__', 'playwright',
|
|
142
|
-
'__selenium_unwrapped', '__selenium_evaluate', '__webdriver_evaluate',
|
|
143
|
-
'__driver_evaluate', '_phantom', '__nightmare', 'callPhantom',
|
|
144
|
-
'__webdriver_script_fn', '__webdriver_script_func', 'cdc_adoQpoasnfa76pfcZLmcfl_Array',
|
|
145
|
-
'cdc_adoQpoasnfa76pfcZLmcfl_Promise', 'cdc_adoQpoasnfa76pfcZLmcfl_Symbol'
|
|
146
|
-
];
|
|
147
|
-
automationProps.forEach(prop => {
|
|
148
|
-
try { delete window[prop]; } catch (e) { }
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// ============ ERROR STACK SANITIZATION ============
|
|
152
|
-
const originalError = Error;
|
|
153
|
-
window.Error = function (...args) {
|
|
154
|
-
const error = new originalError(...args);
|
|
155
|
-
if (error.stack) {
|
|
156
|
-
error.stack = error.stack.split('\\n')
|
|
157
|
-
.filter(line => !line.includes('puppeteer') && !line.includes('playwright') && !line.includes('UtilityScript'))
|
|
158
|
-
.join('\\n');
|
|
159
|
-
}
|
|
160
|
-
return error;
|
|
161
|
-
};
|
|
162
|
-
window.Error.prototype = originalError.prototype;
|
|
163
|
-
|
|
164
|
-
// ============ HARDWARE SPOOFING ============
|
|
165
|
-
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 4 });
|
|
166
|
-
Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
|
|
167
|
-
|
|
168
|
-
// ============ CONNECTION API ============
|
|
169
|
-
if (navigator.connection) {
|
|
170
|
-
Object.defineProperty(navigator.connection, 'rtt', { get: () => 50 });
|
|
171
|
-
Object.defineProperty(navigator.connection, 'downlink', { get: () => 10 });
|
|
172
|
-
Object.defineProperty(navigator.connection, 'effectiveType', { get: () => '4g' });
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// MouseEvent screenX/screenY fix
|
|
177
|
-
// 🛡️ COMPREHENSIVE STEALTH INJECTION - For Brotector/Datadome bypass
|
|
178
|
-
await page.evaluateOnNewDocument(() => {
|
|
179
|
-
// ============ NAVIGATOR WEBDRIVER ELIMINATION ============
|
|
180
|
-
Object.defineProperty(navigator, 'webdriver', {
|
|
181
|
-
get: () => undefined,
|
|
182
|
-
configurable: true
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// Delete webdriver from Object.getOwnPropertyDescriptor
|
|
186
|
-
const originalGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
187
|
-
Object.getOwnPropertyDescriptor = function (obj, prop) {
|
|
188
|
-
if (obj === navigator && prop === 'webdriver') return undefined;
|
|
189
|
-
return originalGetOwnPropertyDescriptor.apply(this, arguments);
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
// ============ PLUGINS & MIME TYPES ============
|
|
193
|
-
Object.defineProperty(navigator, 'plugins', {
|
|
194
|
-
get: () => {
|
|
195
|
-
const plugins = [
|
|
196
|
-
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
|
|
197
|
-
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
|
|
198
|
-
{ name: 'Native Client', filename: 'internal-nacl-plugin', description: '' }
|
|
199
|
-
];
|
|
200
|
-
plugins.length = 3;
|
|
201
|
-
plugins.item = i => plugins[i];
|
|
202
|
-
plugins.namedItem = n => plugins.find(p => p.name === n);
|
|
203
|
-
plugins.refresh = () => { };
|
|
204
|
-
return plugins;
|
|
205
|
-
},
|
|
206
|
-
configurable: true
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
Object.defineProperty(navigator, 'mimeTypes', {
|
|
210
|
-
get: () => {
|
|
211
|
-
const mimeTypes = [
|
|
212
|
-
{ type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format' },
|
|
213
|
-
{ type: 'text/pdf', suffixes: 'pdf', description: '' }
|
|
214
|
-
];
|
|
215
|
-
mimeTypes.length = 2;
|
|
216
|
-
mimeTypes.item = i => mimeTypes[i];
|
|
217
|
-
mimeTypes.namedItem = n => mimeTypes.find(m => m.type === n);
|
|
218
|
-
return mimeTypes;
|
|
219
|
-
},
|
|
220
|
-
configurable: true
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// ============ LANGUAGES ============
|
|
224
|
-
Object.defineProperty(navigator, 'languages', {
|
|
225
|
-
get: () => ['en-US', 'en'],
|
|
226
|
-
configurable: true
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
// ============ CHROME RUNTIME REMOVAL ============
|
|
230
|
-
if (window.chrome) {
|
|
231
|
-
const originalChrome = window.chrome;
|
|
232
|
-
window.chrome = new Proxy(originalChrome, {
|
|
233
|
-
get(target, prop) {
|
|
234
|
-
if (prop === 'runtime') return undefined;
|
|
235
|
-
if (prop === 'csi') return undefined;
|
|
236
|
-
if (prop === 'loadTimes') return undefined;
|
|
237
|
-
return Reflect.get(target, prop);
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// ============ PERMISSIONS API ============
|
|
243
|
-
if (navigator.permissions) {
|
|
244
|
-
const originalQuery = navigator.permissions.query.bind(navigator.permissions);
|
|
245
|
-
navigator.permissions.query = (params) => {
|
|
246
|
-
if (params.name === 'notifications') {
|
|
247
|
-
return Promise.resolve({ state: 'prompt', onchange: null });
|
|
248
|
-
}
|
|
249
|
-
return originalQuery(params);
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// ============ AUTOMATION SIGNATURES REMOVAL ============
|
|
254
|
-
const automationProps = [
|
|
255
|
-
'__puppeteer__', 'puppeteer', '__playwright__', 'playwright',
|
|
256
|
-
'__selenium_unwrapped', '__selenium_evaluate', '__webdriver_evaluate',
|
|
257
|
-
'__driver_evaluate', '_phantom', '__nightmare', 'callPhantom',
|
|
258
|
-
'__webdriver_script_fn', '__webdriver_script_func', 'cdc_adoQpoasnfa76pfcZLmcfl_Array',
|
|
259
|
-
'cdc_adoQpoasnfa76pfcZLmcfl_Promise', 'cdc_adoQpoasnfa76pfcZLmcfl_Symbol'
|
|
260
|
-
];
|
|
261
|
-
automationProps.forEach(prop => {
|
|
262
|
-
try { delete window[prop]; } catch (e) { }
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
// ============ ERROR STACK SANITIZATION ============
|
|
266
|
-
const originalError = Error;
|
|
267
|
-
window.Error = function (...args) {
|
|
268
|
-
const error = new originalError(...args);
|
|
269
|
-
if (error.stack) {
|
|
270
|
-
error.stack = error.stack.split('\\n')
|
|
271
|
-
.filter(line => !line.includes('puppeteer') && !line.includes('playwright') && !line.includes('UtilityScript'))
|
|
272
|
-
.join('\\n');
|
|
273
|
-
}
|
|
274
|
-
return error;
|
|
275
|
-
};
|
|
276
|
-
window.Error.prototype = originalError.prototype;
|
|
277
|
-
|
|
278
|
-
// ============ HARDWARE SPOOFING ============
|
|
279
|
-
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 4 });
|
|
280
|
-
Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
|
|
281
|
-
|
|
282
|
-
// ============ CONNECTION API ============
|
|
283
|
-
if (navigator.connection) {
|
|
284
|
-
Object.defineProperty(navigator.connection, 'rtt', { get: () => 50 });
|
|
285
|
-
Object.defineProperty(navigator.connection, 'downlink', { get: () => 10 });
|
|
286
|
-
Object.defineProperty(navigator.connection, 'effectiveType', { get: () => '4g' });
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
|
|
290
41
|
await page.evaluateOnNewDocument(() => {
|
|
291
42
|
Object.defineProperty(MouseEvent.prototype, 'screenX', {
|
|
292
43
|
get: function () {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.9",
|
|
4
4
|
"description": "This package is designed to bypass puppeteer's bot-detecting captchas such as Cloudflare. It acts like a real browser and can be managed with puppeteer.",
|
|
5
5
|
"main": "lib/cjs/index.js",
|
|
6
6
|
"module": "lib/esm/index.mjs",
|
|
@@ -37,9 +37,11 @@
|
|
|
37
37
|
"license": "ISC",
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"brave-real-launcher": "^1.2.48",
|
|
40
|
-
"brave-real-puppeteer-core": "^24.34.0-patch.
|
|
40
|
+
"brave-real-puppeteer-core": "^24.34.0-patch.13",
|
|
41
41
|
"ghost-cursor": "^1.4.1",
|
|
42
42
|
"puppeteer-extra": "^3.3.6",
|
|
43
|
+
"puppeteer-extra-plugin-adblocker": "^2.13.6",
|
|
44
|
+
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
|
43
45
|
"tree-kill": "^1.2.2",
|
|
44
46
|
"xvfb": "^0.4.0"
|
|
45
47
|
},
|
package/test/cjs/test.js
CHANGED
|
@@ -2,11 +2,18 @@ const test = require('node:test');
|
|
|
2
2
|
const assert = require('node:assert');
|
|
3
3
|
const { connect } = require('../../lib/cjs/index.js');
|
|
4
4
|
|
|
5
|
+
// Import Stealth and Adblocker Plugins
|
|
6
|
+
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
|
|
7
|
+
const AdblockerPlugin = require('puppeteer-extra-plugin-adblocker');
|
|
8
|
+
|
|
5
9
|
const realBrowserOption = {
|
|
6
10
|
turnstile: true,
|
|
7
11
|
headless: false,
|
|
8
12
|
customConfig: {},
|
|
9
|
-
plugins: [
|
|
13
|
+
plugins: [
|
|
14
|
+
StealthPlugin(),
|
|
15
|
+
AdblockerPlugin({ blockTrackers: true })
|
|
16
|
+
]
|
|
10
17
|
}
|
|
11
18
|
|
|
12
19
|
// Shared browser instance for all tests
|
|
@@ -38,11 +45,14 @@ test('DrissionPage Detector', async () => {
|
|
|
38
45
|
assert.strictEqual(result, true, "DrissionPage Detector test failed!")
|
|
39
46
|
})
|
|
40
47
|
|
|
41
|
-
test('
|
|
42
|
-
await page.goto("https://
|
|
48
|
+
test('Sannysoft WebDriver Detector', async () => {
|
|
49
|
+
await page.goto("https://bot.sannysoft.com/");
|
|
43
50
|
await new Promise(r => setTimeout(r, 3000));
|
|
44
|
-
let result = await page.evaluate(() => {
|
|
45
|
-
|
|
51
|
+
let result = await page.evaluate(() => {
|
|
52
|
+
const webdriverEl = document.getElementById('webdriver-result');
|
|
53
|
+
return webdriverEl && webdriverEl.classList.contains('passed');
|
|
54
|
+
});
|
|
55
|
+
assert.strictEqual(result, true, "Sannysoft WebDriver Detector test failed! Browser detected as bot.")
|
|
46
56
|
})
|
|
47
57
|
|
|
48
58
|
test('Cloudflare WAF', async () => {
|
|
@@ -117,8 +127,24 @@ test('Datadome Bot Detector', async (t) => {
|
|
|
117
127
|
// If this test fails, please first check if you can access https://antcpt.com/score_detector/
|
|
118
128
|
test('Recaptcha V3 Score (hard)', async () => {
|
|
119
129
|
await page.goto("https://antcpt.com/score_detector/");
|
|
130
|
+
|
|
131
|
+
// Human-like warm-up interactions before clicking
|
|
132
|
+
// 1. Random mouse movements using realCursor
|
|
133
|
+
await page.realCursor.move('body', { paddingPercentage: 20 });
|
|
134
|
+
await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
|
|
135
|
+
|
|
136
|
+
// 2. Scroll down a bit to simulate reading
|
|
137
|
+
await page.mouse.wheel({ deltaY: 100 + Math.random() * 100 });
|
|
138
|
+
await new Promise(r => setTimeout(r, 800 + Math.random() * 400));
|
|
139
|
+
|
|
140
|
+
// 3. Move mouse towards button area naturally
|
|
141
|
+
await page.realCursor.move('button', { paddingPercentage: 10 });
|
|
142
|
+
await new Promise(r => setTimeout(r, 300 + Math.random() * 300));
|
|
143
|
+
|
|
144
|
+
// 4. Now click the button
|
|
120
145
|
await page.realClick("button")
|
|
121
|
-
await new Promise(r => setTimeout(r,
|
|
146
|
+
await new Promise(r => setTimeout(r, 6000));
|
|
147
|
+
|
|
122
148
|
const score = await page.evaluate(() => {
|
|
123
149
|
return document.querySelector('big').textContent.replace(/[^0-9.]/g, '')
|
|
124
150
|
})
|
package/test/esm/test.js
CHANGED
|
@@ -2,11 +2,18 @@ import test from 'node:test';
|
|
|
2
2
|
import assert from 'node:assert';
|
|
3
3
|
import { connect } from '../../lib/esm/index.mjs';
|
|
4
4
|
|
|
5
|
+
// Import Stealth and Adblocker Plugins
|
|
6
|
+
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
|
|
7
|
+
import AdblockerPlugin from 'puppeteer-extra-plugin-adblocker';
|
|
8
|
+
|
|
5
9
|
const realBrowserOption = {
|
|
6
10
|
turnstile: true,
|
|
7
11
|
headless: false,
|
|
8
12
|
customConfig: {},
|
|
9
|
-
plugins: [
|
|
13
|
+
plugins: [
|
|
14
|
+
StealthPlugin(),
|
|
15
|
+
AdblockerPlugin({ blockTrackers: true })
|
|
16
|
+
]
|
|
10
17
|
}
|
|
11
18
|
|
|
12
19
|
// Shared browser instance for all tests
|
|
@@ -38,11 +45,14 @@ test('DrissionPage Detector', async () => {
|
|
|
38
45
|
assert.strictEqual(result, true, "DrissionPage Detector test failed!")
|
|
39
46
|
})
|
|
40
47
|
|
|
41
|
-
test('
|
|
42
|
-
await page.goto("https://
|
|
48
|
+
test('Sannysoft WebDriver Detector', async () => {
|
|
49
|
+
await page.goto("https://bot.sannysoft.com/");
|
|
43
50
|
await new Promise(r => setTimeout(r, 3000));
|
|
44
|
-
let result = await page.evaluate(() => {
|
|
45
|
-
|
|
51
|
+
let result = await page.evaluate(() => {
|
|
52
|
+
const webdriverEl = document.getElementById('webdriver-result');
|
|
53
|
+
return webdriverEl && webdriverEl.classList.contains('passed');
|
|
54
|
+
});
|
|
55
|
+
assert.strictEqual(result, true, "Sannysoft WebDriver Detector test failed! Browser detected as bot.")
|
|
46
56
|
})
|
|
47
57
|
|
|
48
58
|
test('Cloudflare WAF', async () => {
|
|
@@ -118,8 +128,24 @@ test('Datadome Bot Detector', async (t) => {
|
|
|
118
128
|
// If this test fails, please first check if you can access https://antcpt.com/score_detector/
|
|
119
129
|
test('Recaptcha V3 Score (hard)', async () => {
|
|
120
130
|
await page.goto("https://antcpt.com/score_detector/");
|
|
131
|
+
|
|
132
|
+
// Human-like warm-up interactions before clicking
|
|
133
|
+
// 1. Random mouse movements using realCursor
|
|
134
|
+
await page.realCursor.move('body', { paddingPercentage: 20 });
|
|
135
|
+
await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
|
|
136
|
+
|
|
137
|
+
// 2. Scroll down a bit to simulate reading
|
|
138
|
+
await page.mouse.wheel({ deltaY: 100 + Math.random() * 100 });
|
|
139
|
+
await new Promise(r => setTimeout(r, 800 + Math.random() * 400));
|
|
140
|
+
|
|
141
|
+
// 3. Move mouse towards button area naturally
|
|
142
|
+
await page.realCursor.move('button', { paddingPercentage: 10 });
|
|
143
|
+
await new Promise(r => setTimeout(r, 300 + Math.random() * 300));
|
|
144
|
+
|
|
145
|
+
// 4. Now click the button
|
|
121
146
|
await page.realClick("button")
|
|
122
|
-
await new Promise(r => setTimeout(r,
|
|
147
|
+
await new Promise(r => setTimeout(r, 6000));
|
|
148
|
+
|
|
123
149
|
const score = await page.evaluate(() => {
|
|
124
150
|
return document.querySelector('big').textContent.replace(/[^0-9.]/g, '')
|
|
125
151
|
})
|
package/data/sdo.gif
DELETED
|
Binary file
|