brave-real-browser 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,69 @@
1
+ const { createCursor } = require('ghost-cursor');
2
+ const { checkTurnstile } = require('./turnstile.js');
3
+ const kill = require('tree-kill');
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
+ async function pageController({ browser, page, proxy, turnstile, xvfbsession, pid, plugins, killProcess = false, chrome }) {
12
+
13
+ let solveStatus = turnstile
14
+
15
+ page.on('close', () => {
16
+ solveStatus = false
17
+ });
18
+
19
+
20
+ browser.on('disconnected', async () => {
21
+ solveStatus = false
22
+ if (killProcess === true) {
23
+ if (xvfbsession) try { xvfbsession.stopSync() } catch (err) { }
24
+ if (chrome) try { chrome.kill() } catch (err) { console.log(err); }
25
+ if (pid) try { kill(pid, 'SIGKILL', () => { }) } catch (err) { }
26
+ }
27
+ });
28
+
29
+ async function turnstileSolver() {
30
+ while (solveStatus) {
31
+ await checkTurnstile({ page }).catch(() => { });
32
+ await new Promise(r => setTimeout(r, 1000));
33
+ }
34
+ return
35
+ }
36
+
37
+ turnstileSolver()
38
+
39
+ if (proxy.username && proxy.password) await page.authenticate({ username: proxy.username, password: proxy.password });
40
+
41
+ if (plugins.length > 0) {
42
+ for (const plugin of plugins) {
43
+ plugin.onPageCreated(page)
44
+ }
45
+ }
46
+
47
+ await page.evaluateOnNewDocument(() => {
48
+ Object.defineProperty(MouseEvent.prototype, 'screenX', {
49
+ get: function () {
50
+ return this.clientX + window.screenX;
51
+ }
52
+ });
53
+
54
+ Object.defineProperty(MouseEvent.prototype, 'screenY', {
55
+ get: function () {
56
+ return this.clientY + window.screenY;
57
+ }
58
+ });
59
+
60
+ });
61
+
62
+ const cursor = createCursor(page);
63
+ page.realCursor = cursor
64
+ page.realClick = cursor.click
65
+
66
+ return page
67
+ }
68
+
69
+ module.exports = { pageController }
@@ -0,0 +1,63 @@
1
+ const checkTurnstile = ({ page }) => {
2
+ return new Promise(async (resolve, reject) => {
3
+ var waitInterval = setTimeout(() => { clearInterval(waitInterval); resolve(false) }, 5000);
4
+ try {
5
+ const elements = await page.$$('[name="cf-turnstile-response"]');
6
+ if (elements.length <= 0) {
7
+
8
+ const coordinates = await page.evaluate(() => {
9
+ let coordinates = [];
10
+ document.querySelectorAll('div').forEach(item => {
11
+ try {
12
+ let itemCoordinates = item.getBoundingClientRect()
13
+ let itemCss = window.getComputedStyle(item)
14
+ if (itemCss.margin == "0px" && itemCss.padding == "0px" && itemCoordinates.width > 290 && itemCoordinates.width <= 310 && !item.querySelector('*')) {
15
+ coordinates.push({ x: itemCoordinates.x, y: item.getBoundingClientRect().y, w: item.getBoundingClientRect().width, h: item.getBoundingClientRect().height })
16
+ }
17
+ } catch (err) { }
18
+ });
19
+
20
+ if (coordinates.length <= 0) {
21
+ document.querySelectorAll('div').forEach(item => {
22
+ try {
23
+ let itemCoordinates = item.getBoundingClientRect()
24
+ if (itemCoordinates.width > 290 && itemCoordinates.width <= 310 && !item.querySelector('*')) {
25
+ coordinates.push({ x: itemCoordinates.x, y: item.getBoundingClientRect().y, w: item.getBoundingClientRect().width, h: item.getBoundingClientRect().height })
26
+ }
27
+ } catch (err) { }
28
+ });
29
+
30
+ }
31
+
32
+ return coordinates
33
+ })
34
+
35
+ for (const item of coordinates) {
36
+ try {
37
+ let x = item.x + 30;
38
+ let y = item.y + item.h / 2;
39
+ await page.mouse.click(x, y);
40
+ } catch (err) { }
41
+ }
42
+ return resolve(true)
43
+ }
44
+
45
+ for (const element of elements) {
46
+ try {
47
+ const parentElement = await element.evaluateHandle(el => el.parentElement);
48
+ const box = await parentElement.boundingBox();
49
+ let x = box.x + 30;
50
+ let y = box.y + box.height / 2;
51
+ await page.mouse.click(x, y);
52
+ } catch (err) { }
53
+ }
54
+ clearInterval(waitInterval)
55
+ resolve(true)
56
+ } catch (err) {
57
+ clearInterval(waitInterval)
58
+ resolve(false)
59
+ }
60
+ })
61
+ }
62
+
63
+ module.exports = { checkTurnstile }
@@ -0,0 +1,116 @@
1
+ import { launch, DEFAULT_FLAGS } from "brave-real-launcher";
2
+ import puppeteer from "brave-real-puppeteer-core";
3
+ import { pageController } from "./module/pageController.mjs";
4
+
5
+ // process.env.REBROWSER_PATCHES_DEBUG=1
6
+ export async function connect({
7
+ args = [],
8
+ headless = false,
9
+ customConfig = {},
10
+ proxy = {},
11
+ turnstile = false,
12
+ connectOption = {},
13
+ disableXvfb = false,
14
+ plugins = [],
15
+ ignoreAllFlags = false,
16
+ } = {}) {
17
+ let xvfbsession = null;
18
+ if (headless == "auto") headless = false;
19
+
20
+ if (process.platform === "linux" && disableXvfb === false) {
21
+ try {
22
+ const { default: Xvfb } = await import("xvfb");
23
+ xvfbsession = new Xvfb({
24
+ silent: true,
25
+ xvfb_args: ["-screen", "0", "1920x1080x24", "-ac"],
26
+ });
27
+ xvfbsession.startSync();
28
+ } catch (err) {
29
+ console.log(
30
+ "You are running on a Linux platform but do not have xvfb installed. The browser can be captured. Please install it with the following command\n\nsudo apt-get install xvfb\n\n" +
31
+ err.message
32
+ );
33
+ }
34
+ }
35
+
36
+ let chromeFlags;
37
+ if (ignoreAllFlags === true) {
38
+ chromeFlags = [
39
+ ...args,
40
+ ...(headless !== false ? [`--headless=${headless}`] : []),
41
+ ...(proxy && proxy.host && proxy.port
42
+ ? [`--proxy-server=${proxy.host}:${proxy.port}`]
43
+ : []),
44
+ ];
45
+ } else {
46
+ // Default flags: https://github.com/codeiva4u/Brave-Real-Launcher/blob/main/src/flags.ts
47
+ const flags = [...DEFAULT_FLAGS];
48
+ // Add AutomationControlled to "disable-features" flag
49
+ const indexDisableFeatures = flags.findIndex((flag) => flag.startsWith('--disable-features'));
50
+ flags[indexDisableFeatures] = `${flags[indexDisableFeatures]},AutomationControlled`;
51
+ // Remove "disable-component-update" flag
52
+ const indexComponentUpdateFlag = flags.findIndex((flag) => flag.startsWith('--disable-component-update'));
53
+ flags.splice(indexComponentUpdateFlag, 1);
54
+ chromeFlags = [
55
+ ...flags,
56
+ ...args,
57
+ ...(headless !== false ? [`--headless=${headless}`] : []),
58
+ ...(proxy && proxy.host && proxy.port
59
+ ? [`--proxy-server=${proxy.host}:${proxy.port}`]
60
+ : []),
61
+ "--no-sandbox",
62
+ "--disable-dev-shm-usage",
63
+ ];
64
+ }
65
+ const chrome = await launch({
66
+ ignoreDefaultFlags: true,
67
+ chromeFlags,
68
+ ...customConfig,
69
+ });
70
+ let pextra = null;
71
+ if (plugins.length > 0) {
72
+ const { addExtra } = await import("puppeteer-extra");
73
+
74
+ pextra = addExtra(puppeteer);
75
+
76
+ for (const item of plugins) {
77
+ pextra.use(item);
78
+ }
79
+ }
80
+
81
+ const browser = await (pextra ? pextra : puppeteer).connect({
82
+ browserURL: `http://127.0.0.1:${chrome.port}`,
83
+ ...connectOption,
84
+ });
85
+
86
+ let [page] = await browser.pages();
87
+
88
+ let pageControllerConfig = {
89
+ browser,
90
+ page,
91
+ proxy,
92
+ turnstile,
93
+ xvfbsession,
94
+ pid: chrome.pid,
95
+ plugins,
96
+ };
97
+
98
+ page = await pageController({
99
+ ...pageControllerConfig,
100
+ chrome,
101
+ killProcess: true,
102
+ });
103
+
104
+ browser.on("targetcreated", async (target) => {
105
+ if (target.type() === "page") {
106
+ let newPage = await target.page();
107
+ pageControllerConfig.page = newPage;
108
+ newPage = await pageController(pageControllerConfig);
109
+ }
110
+ });
111
+
112
+ return {
113
+ browser,
114
+ page,
115
+ };
116
+ }
@@ -0,0 +1,66 @@
1
+ import { createCursor } from 'ghost-cursor';
2
+ import { checkTurnstile } from './turnstile.mjs';
3
+ import kill from 'tree-kill';
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
+ export async function pageController({ browser, page, proxy, turnstile, xvfbsession, pid, plugins, killProcess = false, chrome }) {
12
+
13
+ let solveStatus = turnstile
14
+
15
+ page.on('close', () => {
16
+ solveStatus = false
17
+ });
18
+
19
+
20
+ browser.on('disconnected', async () => {
21
+ solveStatus = false
22
+ if (killProcess === true) {
23
+ if (xvfbsession) try { xvfbsession.stopSync() } catch (err) { }
24
+ if (chrome) try { chrome.kill() } catch (err) { console.log(err); }
25
+ if (pid) try { kill(pid, 'SIGKILL', () => { }) } catch (err) { }
26
+ }
27
+ });
28
+
29
+ async function turnstileSolver() {
30
+ while (solveStatus) {
31
+ await checkTurnstile({ page }).catch(() => { });
32
+ await new Promise(r => setTimeout(r, 1000));
33
+ }
34
+ return
35
+ }
36
+
37
+ turnstileSolver()
38
+
39
+ if (proxy.username && proxy.password) await page.authenticate({ username: proxy.username, password: proxy.password });
40
+
41
+ if (plugins.length > 0) {
42
+ for (const plugin of plugins) {
43
+ plugin.onPageCreated(page)
44
+ }
45
+ }
46
+
47
+ await page.evaluateOnNewDocument(() => {
48
+ Object.defineProperty(MouseEvent.prototype, 'screenX', {
49
+ get: function () {
50
+ return this.clientX + window.screenX;
51
+ }
52
+ });
53
+
54
+ Object.defineProperty(MouseEvent.prototype, 'screenY', {
55
+ get: function () {
56
+ return this.clientY + window.screenY;
57
+ }
58
+ });
59
+
60
+ });
61
+
62
+ const cursor = createCursor(page);
63
+ page.realCursor = cursor
64
+ page.realClick = cursor.click
65
+ return page
66
+ }
@@ -0,0 +1,62 @@
1
+ export const checkTurnstile = ({ page }) => {
2
+ return new Promise(async (resolve, reject) => {
3
+ var waitInterval = setTimeout(() => { clearInterval(waitInterval); resolve(false) }, 5000);
4
+
5
+ try {
6
+ const elements = await page.$$('[name="cf-turnstile-response"]');
7
+ if (elements.length <= 0) {
8
+
9
+ const coordinates = await page.evaluate(() => {
10
+ let coordinates = [];
11
+ document.querySelectorAll('div').forEach(item => {
12
+ try {
13
+ let itemCoordinates = item.getBoundingClientRect()
14
+ let itemCss = window.getComputedStyle(item)
15
+ if (itemCss.margin == "0px" && itemCss.padding == "0px" && itemCoordinates.width > 290 && itemCoordinates.width <= 310 && !item.querySelector('*')) {
16
+ coordinates.push({ x: itemCoordinates.x, y: item.getBoundingClientRect().y, w: item.getBoundingClientRect().width, h: item.getBoundingClientRect().height })
17
+ }
18
+ } catch (err) { }
19
+ });
20
+
21
+ if (coordinates.length <= 0) {
22
+ document.querySelectorAll('div').forEach(item => {
23
+ try {
24
+ let itemCoordinates = item.getBoundingClientRect()
25
+ if (itemCoordinates.width > 290 && itemCoordinates.width <= 310 && !item.querySelector('*')) {
26
+ coordinates.push({ x: itemCoordinates.x, y: item.getBoundingClientRect().y, w: item.getBoundingClientRect().width, h: item.getBoundingClientRect().height })
27
+ }
28
+ } catch (err) { }
29
+ });
30
+
31
+ }
32
+
33
+ return coordinates
34
+ })
35
+
36
+ for (const item of coordinates) {
37
+ try {
38
+ let x = item.x + 30;
39
+ let y = item.y + item.h / 2;
40
+ await page.mouse.click(x, y);
41
+ } catch (err) { }
42
+ }
43
+ return resolve(true)
44
+ }
45
+
46
+ for (const element of elements) {
47
+ try {
48
+ const parentElement = await element.evaluateHandle(el => el.parentElement);
49
+ const box = await parentElement.boundingBox();
50
+ let x = box.x + 30;
51
+ let y = box.y + box.height / 2;
52
+ await page.mouse.click(x, y);
53
+ } catch (err) { }
54
+ }
55
+ clearInterval(waitInterval)
56
+ resolve(true)
57
+ } catch (err) {
58
+ clearInterval(waitInterval)
59
+ resolve(false)
60
+ }
61
+ })
62
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "brave-real-browser",
3
+ "version": "1.5.1",
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
+ "main": "lib/cjs/index.js",
6
+ "module": "lib/esm/index.mjs",
7
+ "exports": {
8
+ ".": {
9
+ "require": "./lib/cjs/index.js",
10
+ "import": "./lib/esm/index.mjs",
11
+ "types": "./typings.d.ts"
12
+ }
13
+ },
14
+ "typings": "typings.d.ts",
15
+ "scripts": {
16
+ "esm_test": "node ./test/esm/test.js",
17
+ "cjs_test": "node ./test/cjs/test.js"
18
+ },
19
+ "keywords": [
20
+ "cf-bypass",
21
+ "cloudflare-bypass",
22
+ "puppeteer-fingerprint",
23
+ "puppeteer-cloudflare",
24
+ "puppeteer-real-browser",
25
+ "undetectable-puppeteer",
26
+ "undetect",
27
+ "undetectable",
28
+ "puppeteer-undetectable",
29
+ "puppeteer-undetect",
30
+ "puppeteer-undetectable-bypass",
31
+ "puppeteer-undetect-bypass",
32
+ "puppeteer-undetectable-cloudflare",
33
+ "puppeteer-undetect-cloudflare",
34
+ "puppeteer-undetectable-cf",
35
+ "puppeteer-undetect-cf",
36
+ "puppeteer-undetectable-cf-bypass"
37
+ ],
38
+ "author": "zfc-software",
39
+ "license": "ISC",
40
+ "dependencies": {
41
+ "brave-real-launcher": "^1.2.8",
42
+ "brave-real-puppeteer-core": "^24.22.0",
43
+ "ghost-cursor": "^1.4.1 ",
44
+ "puppeteer-extra": "^3.3.6",
45
+ "tree-kill": "^1.2.2",
46
+ "xvfb": "^0.4.0"
47
+ },
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/zfcsoftware/puppeteer-real-browser"
51
+ },
52
+ "readme": "README.md",
53
+ "homepage": "https://github.com/zfcsoftware/puppeteer-real-browser"
54
+ }
@@ -0,0 +1,120 @@
1
+ const test = require('node:test');
2
+ const assert = require('node:assert');
3
+ const { connect } = require('../../lib/cjs/index.js');
4
+
5
+
6
+ const realBrowserOption = {
7
+ args: ["--start-maximized"],
8
+ turnstile: true,
9
+ headless: false,
10
+ // disableXvfb: true,
11
+ customConfig: {},
12
+ connectOption: {
13
+ defaultViewport: null
14
+ },
15
+ plugins: []
16
+ }
17
+
18
+ // test('Puppeteer Extra Plugin', async () => {
19
+ // /*
20
+ // Run with:
21
+ // npm i puppeteer-extra-plugin-click-and-wait
22
+ // */
23
+ // realBrowserOption.plugins = [
24
+ // require('puppeteer-extra-plugin-click-and-wait')()
25
+ // ]
26
+ // const { page, browser } = await connect(realBrowserOption)
27
+ // await page.goto("https://google.com", { waitUntil: "domcontentloaded" })
28
+ // await page.clickAndWaitForNavigation('body')
29
+ // await browser.close()
30
+ // })
31
+
32
+ test('DrissionPage Detector', async () => {
33
+ const { page, browser } = await connect(realBrowserOption)
34
+ await page.goto("https://web.archive.org/web/20240913054632/https://drissionpage.pages.dev/");
35
+ await page.realClick("#detector")
36
+ let result = await page.evaluate(() => { return document.querySelector('#isBot span').textContent.includes("not") ? true : false })
37
+ await browser.close()
38
+ assert.strictEqual(result, true, "DrissionPage Detector test failed!")
39
+ })
40
+
41
+ test('Brotector, a webdriver detector', async () => {
42
+ const { page, browser } = await connect(realBrowserOption)
43
+ await page.goto("https://kaliiiiiiiiii.github.io/brotector/");
44
+ await new Promise(r => setTimeout(r, 3000));
45
+ let result = await page.evaluate(() => { return document.querySelector('#table-keys').getAttribute('bgcolor') })
46
+ await browser.close()
47
+ assert.strictEqual(result === "darkgreen", true, "Brotector, a webdriver detector test failed!")
48
+ })
49
+
50
+ test('Cloudflare WAF', async () => {
51
+ const { page, browser } = await connect(realBrowserOption)
52
+ await page.goto("https://nopecha.com/demo/cloudflare");
53
+ let verify = null
54
+ let startDate = Date.now()
55
+ while (!verify && (Date.now() - startDate) < 30000) {
56
+ verify = await page.evaluate(() => { return document.querySelector('.link_row') ? true : null }).catch(() => null)
57
+ await new Promise(r => setTimeout(r, 1000));
58
+ }
59
+ await browser.close()
60
+ assert.strictEqual(verify === true, true, "Cloudflare WAF test failed!")
61
+ })
62
+
63
+
64
+ test('Cloudflare Turnstile', async () => {
65
+ const { page, browser } = await connect(realBrowserOption)
66
+ await page.goto("https://turnstile.zeroclover.io/");
67
+ await page.waitForSelector('[type="submit"]')
68
+ let token = null
69
+ let startDate = Date.now()
70
+ while (!token && (Date.now() - startDate) < 30000) {
71
+ token = await page.evaluate(() => {
72
+ try {
73
+ let item = document.querySelector('[name="cf-turnstile-response"]').value
74
+ return item && item.length > 20 ? item : null
75
+ } catch (e) {
76
+ return null
77
+ }
78
+ })
79
+ await new Promise(r => setTimeout(r, 1000));
80
+ }
81
+ await browser.close()
82
+ // if (token !== null) console.log('Cloudflare Turnstile Token: ' + token);
83
+ assert.strictEqual(token !== null, true, "Cloudflare turnstile test failed!")
84
+ })
85
+
86
+
87
+ test('Fingerprint JS Bot Detector', async () => {
88
+ const { page, browser } = await connect(realBrowserOption)
89
+ await page.goto("https://fingerprint.com/products/bot-detection/");
90
+ await new Promise(r => setTimeout(r, 5000));
91
+ const detect = await page.evaluate(() => {
92
+ return document.querySelector('.HeroSection-module--botSubTitle--2711e').textContent.includes("not") ? true : false
93
+ })
94
+ await browser.close()
95
+ assert.strictEqual(detect, true, "Fingerprint JS Bot Detector test failed!")
96
+ })
97
+
98
+
99
+ // If you fail this test, your ip address probably has a high spam score. Please use a mobile or clean ip address.
100
+ test('Datadome Bot Detector', async (t) => {
101
+ const { page, browser } = await connect(realBrowserOption)
102
+ await page.goto("https://antoinevastel.com/bots/datadome");
103
+ const check = await page.waitForSelector('nav #navbarCollapse').catch(() => null)
104
+ await browser.close()
105
+ assert.strictEqual(check ? true : false, true, "Datadome Bot Detector test failed! [This may also be because your ip address has a high spam score. Please try with a clean ip address.]")
106
+ })
107
+
108
+ // If this test fails, please first check if you can access https://antcpt.com/score_detector/
109
+ test('Recaptcha V3 Score (hard)', async () => {
110
+ const { page, browser } = await connect(realBrowserOption)
111
+ await page.goto("https://antcpt.com/score_detector/");
112
+ await page.realClick("button")
113
+ await new Promise(r => setTimeout(r, 5000));
114
+ const score = await page.evaluate(() => {
115
+ return document.querySelector('big').textContent.replace(/[^0-9.]/g, '')
116
+ })
117
+ await browser.close()
118
+ // if (Number(score) >= 0.7) console.log('Recaptcha V3 Score: ' + score);
119
+ assert.strictEqual(Number(score) >= 0.7, true, "(please first check if you can access https://antcpt.com/score_detector/.) Recaptcha V3 Score (hard) should be >=0.7. Score Result: " + score)
120
+ })
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "test",
3
+ "type": "module",
4
+ "version": "1.0.0",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC",
12
+ "description": ""
13
+ }