Samuel commited on
Commit
15f7aec
·
1 Parent(s): cbbacff
.replit ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ modules = ["nodejs-20"]
2
+ [nix]
3
+ channel = "stable-25_05"
4
+ packages = ["unzip", "chromium", "xvfb-run", "xorg.libX11", "xorg.libXcomposite", "xorg.libXcursor", "xorg.libXdamage", "xorg.libXext", "xorg.libXi", "xorg.libXrandr", "xorg.libXtst", "xorg.libxcb", "nss", "nspr", "alsa-lib", "at-spi2-atk", "cups", "dbus", "libdrm", "mesa", "pango", "jq"]
5
+
6
+ [agent]
7
+ expertMode = true
8
+
9
+ [workflows]
10
+ runButton = "Project"
11
+
12
+ [[workflows.workflow]]
13
+ name = "Project"
14
+ mode = "parallel"
15
+ author = "agent"
16
+
17
+ [[workflows.workflow.tasks]]
18
+ task = "workflow.run"
19
+ args = "CF Bypass Server"
20
+
21
+ [[workflows.workflow]]
22
+ name = "CF Bypass Server"
23
+ author = "agent"
24
+
25
+ [workflows.workflow.metadata]
26
+ outputType = "console"
27
+
28
+ [[workflows.workflow.tasks]]
29
+ task = "shell.exec"
30
+ args = "xvfb-run --server-args=\"-screen 0 1280x720x24\" bash -c 'PORT=3000 node index.js'"
31
+ waitForPort = 3000
32
+
33
+ [[ports]]
34
+ localPort = 3000
35
+ externalPort = 3000
36
+
37
+ [[ports]]
38
+ localPort = 45359
39
+ externalPort = 80
Dockerfile ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:latest
2
+
3
+ RUN apt-get update && apt-get install -y \
4
+ wget \
5
+ gnupg \
6
+ ca-certificates \
7
+ apt-transport-https \
8
+ chromium \
9
+ chromium-driver \
10
+ xvfb \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ ENV CHROME_BIN=/usr/bin/chromium
14
+
15
+ WORKDIR /app
16
+
17
+ COPY package*.json ./
18
+
19
+ RUN npm update
20
+ RUN npm install
21
+ RUN npm i -g pm2
22
+ COPY . .
23
+
24
+ EXPOSE 3000
25
+
26
+ CMD ["pm2-runtime", "index.js"]
bun.lock ADDED
The diff for this file is too large to render. See raw diff
 
data/fakePage.html ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title></title>
8
+ </head>
9
+
10
+ <body>
11
+ <div class="turnstile"></div>
12
+ <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback" defer></script>
13
+ <script>
14
+ window.onloadTurnstileCallback = function () {
15
+ turnstile.render('.turnstile', {
16
+ sitekey: '<site-key>',
17
+ callback: function (token) {
18
+ var c = document.createElement('input');
19
+ c.type = 'hidden';
20
+ c.name = 'cf-response';
21
+ c.value = token;
22
+ document.body.appendChild(c);
23
+ },
24
+ });
25
+ };
26
+
27
+ </script>
28
+ </body>
29
+
30
+ </html>
endpoints/getSource.js ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function getSource({ url, proxy }) {
2
+ return new Promise(async (resolve, reject) => {
3
+ if (!url) return reject("Missing url parameter");
4
+ const context = await global.browser
5
+ .createBrowserContext({
6
+ proxyServer: proxy ? `http://${proxy.host}:${proxy.port}` : undefined, // https://pptr.dev/api/puppeteer.browsercontextoptions
7
+ })
8
+ .catch(() => null);
9
+ if (!context) return reject("Failed to create browser context");
10
+
11
+ let isResolved = false;
12
+
13
+ var cl = setTimeout(async () => {
14
+ if (!isResolved) {
15
+ await context.close();
16
+ reject("Timeout Error");
17
+ }
18
+ }, global.timeOut || 60000);
19
+
20
+ try {
21
+ const page = await context.newPage();
22
+
23
+ if (proxy?.username && proxy?.password)
24
+ await page.authenticate({
25
+ username: proxy.username,
26
+ password: proxy.password,
27
+ });
28
+
29
+ await page.setRequestInterception(true);
30
+ page.on("request", async (request) => request.continue());
31
+ page.on("response", async (res) => {
32
+ try {
33
+ if (
34
+ [200, 302].includes(res.status()) &&
35
+ [url, url + "/"].includes(res.url())
36
+ ) {
37
+ await page
38
+ .waitForNavigation({ waitUntil: "load", timeout: 5000 })
39
+ .catch(() => {});
40
+ const html = await page.content();
41
+ await context.close();
42
+ isResolved = true;
43
+ clearInterval(cl);
44
+ resolve(html);
45
+ }
46
+ } catch (e) {}
47
+ });
48
+ await page.goto(url, {
49
+ waitUntil: "domcontentloaded",
50
+ });
51
+ } catch (e) {
52
+ if (!isResolved) {
53
+ await context.close();
54
+ clearInterval(cl);
55
+ reject(e.message);
56
+ }
57
+ }
58
+ });
59
+ }
60
+ module.exports = getSource;
endpoints/proxyRequest.js ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function proxyRequest({ url, proxy, method = 'GET', body = null, headers = {}, cookies = [], sessionHeaders = {} }) {
2
+ return new Promise(async (resolve, reject) => {
3
+ if (!url) return reject("Missing url parameter");
4
+
5
+ const context = await global.browser
6
+ .createBrowserContext({
7
+ proxyServer: proxy ? `http://${proxy.host}:${proxy.port}` : undefined,
8
+ })
9
+ .catch(() => null);
10
+
11
+ if (!context) return reject("Failed to create browser context");
12
+
13
+ let isResolved = false;
14
+
15
+ var cl = setTimeout(async () => {
16
+ if (!isResolved) {
17
+ await context.close();
18
+ reject("Timeout Error");
19
+ }
20
+ }, global.timeOut || 60000);
21
+
22
+ try {
23
+ const page = await context.newPage();
24
+
25
+ if (proxy?.username && proxy?.password)
26
+ await page.authenticate({
27
+ username: proxy.username,
28
+ password: proxy.password,
29
+ });
30
+
31
+ const targetUrl = new URL(url);
32
+
33
+ if (cookies && cookies.length > 0) {
34
+ const cookiesToSet = cookies.map(cookie => ({
35
+ name: cookie.name,
36
+ value: cookie.value,
37
+ domain: cookie.domain || targetUrl.hostname,
38
+ path: cookie.path || '/',
39
+ secure: cookie.secure !== undefined ? cookie.secure : targetUrl.protocol === 'https:',
40
+ httpOnly: cookie.httpOnly || false,
41
+ sameSite: cookie.sameSite || 'Lax'
42
+ }));
43
+ await page.setCookie(...cookiesToSet);
44
+ }
45
+
46
+ if (sessionHeaders && sessionHeaders['user-agent']) {
47
+ await page.setUserAgent(sessionHeaders['user-agent']);
48
+ }
49
+
50
+ const sanitizedHeaders = { ...headers };
51
+ const headersToRemove = ['host', 'content-length', 'connection', 'accept-encoding', 'transfer-encoding'];
52
+ headersToRemove.forEach(h => {
53
+ delete sanitizedHeaders[h];
54
+ delete sanitizedHeaders[h.toLowerCase()];
55
+ });
56
+
57
+ if (sessionHeaders) {
58
+ if (sessionHeaders['accept-language'] && !sanitizedHeaders['accept-language']) {
59
+ sanitizedHeaders['accept-language'] = sessionHeaders['accept-language'];
60
+ }
61
+ }
62
+
63
+ await page.goto(targetUrl.origin, { waitUntil: 'domcontentloaded', timeout: 30000 }).catch(() => {});
64
+
65
+ const result = await page.evaluate(async (options) => {
66
+ try {
67
+ const fetchOptions = {
68
+ method: options.method,
69
+ headers: options.headers || {},
70
+ credentials: 'include'
71
+ };
72
+
73
+ if (options.body && ['POST', 'PUT', 'PATCH'].includes(options.method.toUpperCase())) {
74
+ fetchOptions.body = typeof options.body === 'string'
75
+ ? options.body
76
+ : JSON.stringify(options.body);
77
+ if (typeof options.body === 'object' && !fetchOptions.headers['content-type']) {
78
+ fetchOptions.headers['content-type'] = 'application/json';
79
+ }
80
+ }
81
+
82
+ const response = await fetch(options.url, fetchOptions);
83
+
84
+ const responseHeaders = {};
85
+ response.headers.forEach((value, key) => {
86
+ responseHeaders[key] = value;
87
+ });
88
+
89
+ let responseBody;
90
+ const contentType = response.headers.get('content-type') || '';
91
+
92
+ if (contentType.includes('application/json')) {
93
+ responseBody = await response.json();
94
+ } else {
95
+ responseBody = await response.text();
96
+ }
97
+
98
+ return {
99
+ status: response.status,
100
+ statusText: response.statusText,
101
+ headers: responseHeaders,
102
+ body: responseBody,
103
+ ok: response.ok
104
+ };
105
+ } catch (e) {
106
+ return { error: e.message };
107
+ }
108
+ }, { url, method, body, headers: sanitizedHeaders });
109
+
110
+ if (result.error) {
111
+ await context.close();
112
+ isResolved = true;
113
+ clearTimeout(cl);
114
+ return reject(result.error);
115
+ }
116
+
117
+ const updatedCookies = await page.cookies();
118
+
119
+ const browserHeaders = await page.evaluate(() => {
120
+ return {
121
+ 'user-agent': navigator.userAgent,
122
+ 'accept-language': navigator.language || navigator.languages.join(',')
123
+ };
124
+ });
125
+
126
+ await context.close();
127
+ isResolved = true;
128
+ clearInterval(cl);
129
+
130
+ resolve({
131
+ response: result,
132
+ cookies: updatedCookies,
133
+ browserHeaders
134
+ });
135
+
136
+ } catch (e) {
137
+ if (!isResolved) {
138
+ await context.close();
139
+ clearTimeout(cl);
140
+ reject(e.message);
141
+ }
142
+ }
143
+ });
144
+ }
145
+
146
+ module.exports = proxyRequest;
endpoints/solveTurnstile.max.js ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const fs = require("fs");
2
+ function solveTurnstileMin({ url, proxy }) {
3
+ return new Promise(async (resolve, reject) => {
4
+ if (!url) return reject("Missing url parameter");
5
+
6
+ const context = await global.browser
7
+ .createBrowserContext({
8
+ proxyServer: proxy ? `http://${proxy.host}:${proxy.port}` : undefined, // https://pptr.dev/api/puppeteer.browsercontextoptions
9
+ })
10
+ .catch(() => null);
11
+
12
+ if (!context) return reject("Failed to create browser context");
13
+
14
+ let isResolved = false;
15
+
16
+ var cl = setTimeout(async () => {
17
+ if (!isResolved) {
18
+ await context.close();
19
+ reject("Timeout Error");
20
+ }
21
+ }, global.timeOut || 60000);
22
+
23
+ try {
24
+ const page = await context.newPage();
25
+
26
+ if (proxy?.username && proxy?.password)
27
+ await page.authenticate({
28
+ username: proxy.username,
29
+ password: proxy.password,
30
+ });
31
+
32
+ await page.evaluateOnNewDocument(() => {
33
+ let token = null;
34
+ async function waitForToken() {
35
+ while (!token) {
36
+ try {
37
+ token = window.turnstile.getResponse();
38
+ } catch (e) {}
39
+ await new Promise((resolve) => setTimeout(resolve, 500));
40
+ }
41
+ var c = document.createElement("input");
42
+ c.type = "hidden";
43
+ c.name = "cf-response";
44
+ c.value = token;
45
+ document.body.appendChild(c);
46
+ }
47
+ waitForToken();
48
+ });
49
+
50
+ await page.goto(url, {
51
+ waitUntil: "domcontentloaded",
52
+ });
53
+
54
+ await page.waitForSelector('[name="cf-response"]', {
55
+ timeout: 60000,
56
+ });
57
+ const token = await page.evaluate(() => {
58
+ try {
59
+ return document.querySelector('[name="cf-response"]').value;
60
+ } catch (e) {
61
+ return null;
62
+ }
63
+ });
64
+ isResolved = true;
65
+ clearInterval(cl);
66
+ await context.close();
67
+ if (!token || token.length < 10) return reject("Failed to get token");
68
+ return resolve(token);
69
+ } catch (e) {
70
+ console.log(e);
71
+
72
+ if (!isResolved) {
73
+ await context.close();
74
+ clearInterval(cl);
75
+ reject(e.message);
76
+ }
77
+ }
78
+ });
79
+ }
80
+ module.exports = solveTurnstileMin;
endpoints/solveTurnstile.min.js ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function solveTurnstileMin({ url, proxy, siteKey }) {
2
+ return new Promise(async (resolve, reject) => {
3
+ if (!url) return reject("Missing url parameter");
4
+ if (!siteKey) return reject("Missing siteKey parameter");
5
+
6
+ const context = await global.browser
7
+ .createBrowserContext({
8
+ proxyServer: proxy ? `http://${proxy.host}:${proxy.port}` : undefined, // https://pptr.dev/api/puppeteer.browsercontextoptions
9
+ })
10
+ .catch(() => null);
11
+ if (!context) return reject("Failed to create browser context");
12
+
13
+ let isResolved = false;
14
+
15
+ var cl = setTimeout(async () => {
16
+ if (!isResolved) {
17
+ await context.close();
18
+ reject("Timeout Error");
19
+ }
20
+ }, global.timeOut || 60000);
21
+
22
+ try {
23
+ const page = await context.newPage();
24
+
25
+ if (proxy?.username && proxy?.password)
26
+ await page.authenticate({
27
+ username: proxy.username,
28
+ password: proxy.password,
29
+ });
30
+
31
+ await page.setRequestInterception(true);
32
+
33
+ page.on("request", async (request) => {
34
+ if (
35
+ [url, url + "/"].includes(request.url()) &&
36
+ request.resourceType() === "document"
37
+ ) {
38
+ const response = await request.respond({
39
+ status: 200,
40
+ contentType: "text/html",
41
+ body: String(
42
+ require("fs").readFileSync("./data/fakePage.html")
43
+ ).replace(/<site-key>/g, siteKey),
44
+ });
45
+ } else {
46
+ await request.continue();
47
+ }
48
+ });
49
+
50
+ await page.goto(url, {
51
+ waitUntil: "domcontentloaded",
52
+ });
53
+
54
+ await page.waitForSelector('[name="cf-response"]', {
55
+ timeout: 60000,
56
+ });
57
+
58
+ const token = await page.evaluate(() => {
59
+ try {
60
+ return document.querySelector('[name="cf-response"]').value;
61
+ } catch (e) {
62
+ return null;
63
+ }
64
+ });
65
+
66
+ isResolved = true;
67
+ clearInterval(cl);
68
+ await context.close();
69
+ if (!token || token.length < 10) return reject("Failed to get token");
70
+ return resolve(token);
71
+ } catch (e) {
72
+ if (!isResolved) {
73
+ await context.close();
74
+ clearInterval(cl);
75
+ reject(e.message);
76
+ }
77
+ }
78
+ });
79
+ }
80
+ module.exports = solveTurnstileMin;
endpoints/wafSession.js ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ async function findAcceptLanguage(page) {
2
+ return await page.evaluate(async () => {
3
+ const result = await fetch("https://httpbin.org/get")
4
+ .then((res) => res.json())
5
+ .then(
6
+ (res) =>
7
+ res.headers["Accept-Language"] || res.headers["accept-language"]
8
+ )
9
+ .catch(() => null);
10
+ return result;
11
+ });
12
+ }
13
+
14
+ function getSource({ url, proxy }) {
15
+ return new Promise(async (resolve, reject) => {
16
+ if (!url) return reject("Missing url parameter");
17
+ const context = await global.browser
18
+ .createBrowserContext({
19
+ proxyServer: proxy ? `http://${proxy.host}:${proxy.port}` : undefined, // https://pptr.dev/api/puppeteer.browsercontextoptions
20
+ })
21
+ .catch(() => null);
22
+ if (!context) return reject("Failed to create browser context");
23
+
24
+ let isResolved = false;
25
+
26
+ var cl = setTimeout(async () => {
27
+ if (!isResolved) {
28
+ await context.close();
29
+ reject("Timeout Error");
30
+ }
31
+ }, global.timeOut || 60000);
32
+
33
+ try {
34
+ const page = await context.newPage();
35
+
36
+ if (proxy?.username && proxy?.password)
37
+ await page.authenticate({
38
+ username: proxy.username,
39
+ password: proxy.password,
40
+ });
41
+ let acceptLanguage = await findAcceptLanguage(page);
42
+ await page.setRequestInterception(true);
43
+ page.on("request", async (request) => request.continue());
44
+ page.on("response", async (res) => {
45
+ try {
46
+ if (
47
+ [200, 302].includes(res.status()) &&
48
+ [url, url + "/"].includes(res.url())
49
+ ) {
50
+ await page
51
+ .waitForNavigation({ waitUntil: "load", timeout: 5000 })
52
+ .catch(() => {});
53
+ const cookies = await page.cookies();
54
+ let headers = await res.request().headers();
55
+ delete headers["content-type"];
56
+ delete headers["accept-encoding"];
57
+ delete headers["accept"];
58
+ delete headers["content-length"];
59
+ headers["accept-language"] = acceptLanguage;
60
+ await context.close();
61
+ isResolved = true;
62
+ clearInterval(cl);
63
+ resolve({ cookies, headers });
64
+ }
65
+ } catch (e) {}
66
+ });
67
+
68
+ await page.goto(url, {
69
+ waitUntil: "domcontentloaded",
70
+ });
71
+ } catch (e) {
72
+ if (!isResolved) {
73
+ await context.close();
74
+ clearInterval(cl);
75
+ reject(e.message);
76
+ }
77
+ }
78
+ });
79
+ }
80
+ module.exports = getSource;
index.js ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express')
2
+ const app = express()
3
+ const port = process.env.PORT || 3939
4
+ const bodyParser = require('body-parser')
5
+ const authToken = process.env.authToken || null
6
+ const cors = require('cors')
7
+ const reqValidate = require('./module/reqValidate')
8
+ const swaggerUi = require('swagger-ui-express')
9
+ const openApiSpec = require('./module/openapi')
10
+
11
+ global.browserLength = 0
12
+ global.browserLimit = Number(process.env.browserLimit) || 20
13
+ global.timeOut = Number(process.env.timeOut || 60000)
14
+
15
+ app.set('json spaces', 2);
16
+ app.use(bodyParser.json({}))
17
+ app.use(bodyParser.urlencoded({ extended: true }))
18
+ app.use(cors())
19
+ if (process.env.NODE_ENV !== 'development') {
20
+ let server = app.listen(port, () => { console.log(`Server running on port ${port}`) })
21
+ try {
22
+ server.timeout = global.timeOut
23
+ } catch (e) { }
24
+ }
25
+ if (process.env.SKIP_LAUNCH != 'true') require('./module/createBrowser')
26
+
27
+ const getSource = require('./endpoints/getSource')
28
+ const solveTurnstileMin = require('./endpoints/solveTurnstile.min')
29
+ const solveTurnstileMax = require('./endpoints/solveTurnstile.max')
30
+ const wafSession = require('./endpoints/wafSession')
31
+ const proxyRequest = require('./endpoints/proxyRequest')
32
+
33
+
34
+ async function handleSolverRequest(req, res) {
35
+ const data = req.method === 'POST' ? { ...req.query, ...req.body } : req.query;
36
+
37
+ const check = reqValidate(data)
38
+
39
+ if (check !== true) return res.status(400).json({ code: 400, message: 'Bad Request', schema: check })
40
+
41
+ if (authToken && data.authToken !== authToken) return res.status(401).json({ code: 401, message: 'Unauthorized' })
42
+
43
+ if (global.browserLength >= global.browserLimit) return res.status(429).json({ code: 429, message: 'Too Many Requests' })
44
+
45
+ if (process.env.SKIP_LAUNCH != 'true' && !global.browser) return res.status(500).json({ code: 500, message: 'The scanner is not ready yet. Please try again a little later.' })
46
+
47
+ var result = { code: 500 }
48
+
49
+ global.browserLength++
50
+
51
+ switch (data.mode) {
52
+ case "source":
53
+ result = await getSource(data).then(res => { return { source: res, code: 200 } }).catch(err => { return { code: 500, message: err.message } })
54
+ break;
55
+ case "turnstile-min":
56
+ result = await solveTurnstileMin(data).then(res => { return { token: res, code: 200 } }).catch(err => { return { code: 500, message: err.message } })
57
+ break;
58
+ case "turnstile-max":
59
+ result = await solveTurnstileMax(data).then(res => { return { token: res, code: 200 } }).catch(err => { return { code: 500, message: err.message } })
60
+ break;
61
+ case "waf-session":
62
+ result = await wafSession(data).then(res => { return { ...res, code: 200 } }).catch(err => { return { code: 500, message: err.message } })
63
+ break;
64
+ case "proxy-request":
65
+ result = await proxyRequest(data).then(res => { return { ...res, code: 200 } }).catch(err => { return { code: 500, message: err.message || err } })
66
+ break;
67
+ }
68
+
69
+ global.browserLength--
70
+
71
+ res.status(result.code ?? 500).send(result)
72
+ }
73
+
74
+ app.get('/solver', handleSolverRequest);
75
+ app.post('/solver', handleSolverRequest);
76
+
77
+ app.get('/api-docs.json', (req, res) => {
78
+ res.json(openApiSpec);
79
+ });
80
+
81
+ app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(openApiSpec));
82
+
83
+ app.use((req, res) => { res.status(404).json({ code: 404, message: 'Not Found' }) })
84
+
85
+ if (process.env.NODE_ENV == 'development') module.exports = app
module/createBrowser.js ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { connect } = require("puppeteer-real-browser")
2
+ async function createBrowser() {
3
+ try {
4
+ if (global.finished == true) return
5
+
6
+ global.browser = null
7
+
8
+ // console.log('Launching the browser...');
9
+
10
+ const { browser } = await connect({
11
+ headless: false,
12
+ turnstile: true,
13
+ connectOption: { defaultViewport: null },
14
+ disableXvfb: false,
15
+ })
16
+
17
+ // console.log('Browser launched');
18
+
19
+ global.browser = browser;
20
+
21
+ browser.on('disconnected', async () => {
22
+ if (global.finished == true) return
23
+ console.log('Browser disconnected');
24
+ await new Promise(resolve => setTimeout(resolve, 3000));
25
+ await createBrowser();
26
+ })
27
+
28
+ } catch (e) {
29
+ console.log(e.message);
30
+ if (global.finished == true) return
31
+ await new Promise(resolve => setTimeout(resolve, 3000));
32
+ await createBrowser();
33
+ }
34
+ }
35
+ createBrowser()
module/openapi.js ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const openApiSpec = {
2
+ openapi: "3.0.3",
3
+ info: {
4
+ title: "CF Bypass Solver API",
5
+ description: "API for solving Cloudflare challenges including Turnstile captchas, WAF sessions, and proxy requests.",
6
+ version: "1.0.0"
7
+ },
8
+ servers: [
9
+ {
10
+ url: "/",
11
+ description: "Current server"
12
+ }
13
+ ],
14
+ paths: {
15
+ "/solver": {
16
+ get: {
17
+ summary: "Solver endpoint (GET)",
18
+ description: "Handle solver requests via GET method. The mode parameter determines which solver to use.",
19
+ parameters: [
20
+ { $ref: "#/components/parameters/mode" },
21
+ { $ref: "#/components/parameters/url" },
22
+ { $ref: "#/components/parameters/authToken" },
23
+ { $ref: "#/components/parameters/siteKey" }
24
+ ],
25
+ responses: {
26
+ 200: { $ref: "#/components/responses/SuccessResponse" },
27
+ 400: { $ref: "#/components/responses/BadRequest" },
28
+ 401: { $ref: "#/components/responses/Unauthorized" },
29
+ 429: { $ref: "#/components/responses/TooManyRequests" },
30
+ 500: { $ref: "#/components/responses/ServerError" }
31
+ }
32
+ },
33
+ post: {
34
+ summary: "Solver endpoint (POST)",
35
+ description: "Handle solver requests via POST method. The mode parameter determines which solver to use.",
36
+ requestBody: {
37
+ required: true,
38
+ content: {
39
+ "application/json": {
40
+ schema: { $ref: "#/components/schemas/SolverRequest" }
41
+ }
42
+ }
43
+ },
44
+ responses: {
45
+ 200: { $ref: "#/components/responses/SuccessResponse" },
46
+ 400: { $ref: "#/components/responses/BadRequest" },
47
+ 401: { $ref: "#/components/responses/Unauthorized" },
48
+ 429: { $ref: "#/components/responses/TooManyRequests" },
49
+ 500: { $ref: "#/components/responses/ServerError" }
50
+ }
51
+ }
52
+ },
53
+ "/api-docs": {
54
+ get: {
55
+ summary: "OpenAPI Documentation UI",
56
+ description: "Interactive Swagger UI documentation for the API.",
57
+ responses: {
58
+ 200: {
59
+ description: "HTML page with Swagger UI"
60
+ }
61
+ }
62
+ }
63
+ },
64
+ "/api-docs.json": {
65
+ get: {
66
+ summary: "OpenAPI JSON Specification",
67
+ description: "Returns the raw OpenAPI specification in JSON format.",
68
+ responses: {
69
+ 200: {
70
+ description: "OpenAPI specification",
71
+ content: {
72
+ "application/json": {
73
+ schema: {
74
+ type: "object"
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+ },
83
+ components: {
84
+ parameters: {
85
+ mode: {
86
+ name: "mode",
87
+ in: "query",
88
+ required: true,
89
+ description: "The solver mode to use",
90
+ schema: {
91
+ type: "string",
92
+ enum: ["source", "turnstile-min", "turnstile-max", "waf-session", "proxy-request"]
93
+ }
94
+ },
95
+ url: {
96
+ name: "url",
97
+ in: "query",
98
+ required: true,
99
+ description: "Target URL to process",
100
+ schema: {
101
+ type: "string",
102
+ format: "uri"
103
+ }
104
+ },
105
+ authToken: {
106
+ name: "authToken",
107
+ in: "query",
108
+ required: false,
109
+ description: "Authentication token (required if server has authToken configured)",
110
+ schema: {
111
+ type: "string"
112
+ }
113
+ },
114
+ siteKey: {
115
+ name: "siteKey",
116
+ in: "query",
117
+ required: false,
118
+ description: "Turnstile site key (required for turnstile modes)",
119
+ schema: {
120
+ type: "string"
121
+ }
122
+ }
123
+ },
124
+ schemas: {
125
+ SolverRequest: {
126
+ type: "object",
127
+ required: ["mode", "url"],
128
+ properties: {
129
+ mode: {
130
+ type: "string",
131
+ enum: ["source", "turnstile-min", "turnstile-max", "waf-session", "proxy-request"],
132
+ description: "The solver mode to use"
133
+ },
134
+ url: {
135
+ type: "string",
136
+ format: "uri",
137
+ description: "Target URL to process"
138
+ },
139
+ authToken: {
140
+ type: "string",
141
+ description: "Authentication token (required if server has authToken configured)"
142
+ },
143
+ siteKey: {
144
+ type: "string",
145
+ description: "Turnstile site key (required for turnstile modes)"
146
+ },
147
+ proxy: {
148
+ type: "object",
149
+ description: "Proxy configuration",
150
+ properties: {
151
+ host: { type: "string", description: "Proxy host" },
152
+ port: { type: "integer", description: "Proxy port" },
153
+ username: { type: "string", description: "Proxy username" },
154
+ password: { type: "string", description: "Proxy password" }
155
+ }
156
+ },
157
+ method: {
158
+ type: "string",
159
+ enum: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
160
+ description: "HTTP method for proxy-request mode"
161
+ },
162
+ body: {
163
+ oneOf: [
164
+ { type: "string" },
165
+ { type: "object" }
166
+ ],
167
+ description: "Request body for proxy-request mode"
168
+ },
169
+ headers: {
170
+ type: "object",
171
+ description: "Custom headers for the request"
172
+ },
173
+ cookies: {
174
+ type: "array",
175
+ description: "Cookies to set for the request",
176
+ items: {
177
+ type: "object",
178
+ required: ["name", "value"],
179
+ properties: {
180
+ name: { type: "string" },
181
+ value: { type: "string" },
182
+ domain: { type: "string" },
183
+ path: { type: "string" },
184
+ secure: { type: "boolean" },
185
+ httpOnly: { type: "boolean" },
186
+ sameSite: { type: "string" }
187
+ }
188
+ }
189
+ },
190
+ sessionHeaders: {
191
+ type: "object",
192
+ description: "Session headers for waf-session mode"
193
+ }
194
+ }
195
+ },
196
+ SourceResponse: {
197
+ type: "object",
198
+ properties: {
199
+ code: { type: "integer", example: 200 },
200
+ source: { type: "string", description: "Page source HTML" }
201
+ }
202
+ },
203
+ TurnstileResponse: {
204
+ type: "object",
205
+ properties: {
206
+ code: { type: "integer", example: 200 },
207
+ token: { type: "string", description: "Solved Turnstile token" }
208
+ }
209
+ },
210
+ WafSessionResponse: {
211
+ type: "object",
212
+ properties: {
213
+ code: { type: "integer", example: 200 },
214
+ cookies: { type: "array", items: { type: "object" } },
215
+ headers: { type: "object" }
216
+ }
217
+ },
218
+ ProxyRequestResponse: {
219
+ type: "object",
220
+ properties: {
221
+ code: { type: "integer", example: 200 },
222
+ status: { type: "integer" },
223
+ headers: { type: "object" },
224
+ body: { type: "string" }
225
+ }
226
+ },
227
+ ErrorResponse: {
228
+ type: "object",
229
+ properties: {
230
+ code: { type: "integer" },
231
+ message: { type: "string" }
232
+ }
233
+ }
234
+ },
235
+ responses: {
236
+ SuccessResponse: {
237
+ description: "Successful response. Content varies based on mode used.",
238
+ content: {
239
+ "application/json": {
240
+ schema: {
241
+ oneOf: [
242
+ { $ref: "#/components/schemas/SourceResponse" },
243
+ { $ref: "#/components/schemas/TurnstileResponse" },
244
+ { $ref: "#/components/schemas/WafSessionResponse" },
245
+ { $ref: "#/components/schemas/ProxyRequestResponse" }
246
+ ]
247
+ }
248
+ }
249
+ }
250
+ },
251
+ BadRequest: {
252
+ description: "Invalid request parameters",
253
+ content: {
254
+ "application/json": {
255
+ schema: {
256
+ type: "object",
257
+ properties: {
258
+ code: { type: "integer", example: 400 },
259
+ message: { type: "string", example: "Bad Request" },
260
+ schema: { type: "array", items: { type: "object" } }
261
+ }
262
+ }
263
+ }
264
+ }
265
+ },
266
+ Unauthorized: {
267
+ description: "Invalid or missing authentication token",
268
+ content: {
269
+ "application/json": {
270
+ schema: { $ref: "#/components/schemas/ErrorResponse" },
271
+ example: { code: 401, message: "Unauthorized" }
272
+ }
273
+ }
274
+ },
275
+ TooManyRequests: {
276
+ description: "Browser limit reached",
277
+ content: {
278
+ "application/json": {
279
+ schema: { $ref: "#/components/schemas/ErrorResponse" },
280
+ example: { code: 429, message: "Too Many Requests" }
281
+ }
282
+ }
283
+ },
284
+ ServerError: {
285
+ description: "Internal server error",
286
+ content: {
287
+ "application/json": {
288
+ schema: { $ref: "#/components/schemas/ErrorResponse" },
289
+ example: { code: 500, message: "Error message" }
290
+ }
291
+ }
292
+ }
293
+ }
294
+ }
295
+ };
296
+
297
+ module.exports = openApiSpec;
module/reqValidate.js ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const Ajv = require("ajv")
2
+ const addFormats = require("ajv-formats")
3
+
4
+ const ajv = new Ajv()
5
+ addFormats(ajv)
6
+
7
+ const schema = {
8
+ "type": "object",
9
+ "properties": {
10
+ "mode": {
11
+ "type": "string",
12
+ "enum": ["source", "turnstile-min", "turnstile-max", "waf-session", "proxy-request"],
13
+ },
14
+ "proxy": {
15
+ "type": "object",
16
+ "properties": {
17
+ "host": { "type": "string" },
18
+ "port": { "type": "integer" },
19
+ "username": { "type": "string" },
20
+ "password": { "type": "string" }
21
+ },
22
+ "additionalProperties": false
23
+ },
24
+ "url": {
25
+ "type": "string",
26
+ "format": "uri",
27
+ },
28
+ "authToken": {
29
+ "type": "string"
30
+ },
31
+ "siteKey": {
32
+ "type": "string"
33
+ },
34
+ "method": {
35
+ "type": "string",
36
+ "enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]
37
+ },
38
+ "body": {
39
+ "type": ["string", "object"]
40
+ },
41
+ "headers": {
42
+ "type": "object"
43
+ },
44
+ "cookies": {
45
+ "type": "array",
46
+ "items": {
47
+ "type": "object",
48
+ "properties": {
49
+ "name": { "type": "string" },
50
+ "value": { "type": "string" },
51
+ "domain": { "type": "string" },
52
+ "path": { "type": "string" },
53
+ "secure": { "type": "boolean" },
54
+ "httpOnly": { "type": "boolean" },
55
+ "sameSite": { "type": "string" }
56
+ },
57
+ "required": ["name", "value"]
58
+ }
59
+ },
60
+ "sessionHeaders": {
61
+ "type": "object"
62
+ }
63
+ },
64
+ "required": ["mode", "url"],
65
+ "additionalProperties": false
66
+ }
67
+
68
+ // const data = {
69
+ // mode: "source",
70
+ // url: "https://example.com",
71
+ // proxy: {
72
+ // host: "localhost",
73
+ // port: 8080,
74
+ // username: "test",
75
+ // password: "test"
76
+ // },
77
+ // authToken: "123456"
78
+ // }
79
+
80
+
81
+ function validate(data) {
82
+ const valid = ajv.validate(schema, data)
83
+ if (!valid) return ajv.errors
84
+ else return true
85
+ }
86
+
87
+ module.exports = validate
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "cf-solver-scraper",
3
+ "version": "2.1.3",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "start": "node index.js",
7
+ "test": "node --experimental-vm-modules ./node_modules/.bin/jest --detectOpenHandles --verbose"
8
+ },
9
+ "jest": {
10
+ "testMatch": [
11
+ "**/tests/**/*.js"
12
+ ],
13
+ "verbose": true
14
+ },
15
+ "keywords": [
16
+ "cf-clearance",
17
+ "cloudflare",
18
+ "waf",
19
+ "scraper",
20
+ "puppeteer",
21
+ "xvfb",
22
+ "turnstile",
23
+ "bypass",
24
+ "undetected",
25
+ "stealth"
26
+ ],
27
+ "author": "zfcsoftware",
28
+ "license": "ISC",
29
+ "description": "This package is an experimental and educational package created for Cloudflare protections.",
30
+ "dependencies": {
31
+ "ajv": "^8.17.1",
32
+ "ajv-formats": "^3.0.1",
33
+ "body-parser": "^1.20.3",
34
+ "cors": "^2.8.5",
35
+ "dotenv": "^16.4.5",
36
+ "express": "^4.21.0",
37
+ "jest": "^29.7.0",
38
+ "puppeteer-real-browser": "^1.4.0",
39
+ "supertest": "^7.0.0",
40
+ "swagger-ui-express": "^5.0.1"
41
+ }
42
+ }
replit.md ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Overview
2
+
3
+ This is a Cloudflare protection bypass service that provides web scraping and bot protection circumvention capabilities. The application acts as an API service that uses headless browser automation (via Puppeteer) to solve Cloudflare challenges, including Turnstile CAPTCHAs and WAF (Web Application Firewall) sessions. It exposes multiple endpoints for different bypass strategies and can operate with or without proxy support.
4
+
5
+ # User Preferences
6
+
7
+ Preferred communication style: Simple, everyday language.
8
+
9
+ # System Architecture
10
+
11
+ ## Backend Architecture
12
+
13
+ **Framework**: Express.js REST API server
14
+ - **Problem**: Need to provide multiple browser automation endpoints as HTTP services
15
+ - **Solution**: Express server with JSON body parsing, CORS support, and configurable timeout handling
16
+ - **Rationale**: Express provides a lightweight, flexible framework for creating API endpoints with middleware support for authentication and request validation
17
+
18
+ **Request Handling Pattern**: Unified handler with mode-based routing
19
+ - **Problem**: Multiple similar endpoints with shared authentication and rate limiting logic
20
+ - **Solution**: Single `handleSolverRequest` function that routes based on `mode` parameter
21
+ - **Alternatives**: Separate route handlers per endpoint
22
+ - **Pros**: Centralized validation, authentication, and rate limiting; reduced code duplication
23
+ - **Cons**: Single handler must accommodate different parameter requirements per mode
24
+
25
+ **Global State Management**: Module-level globals for browser instance and rate limiting
26
+ - **Problem**: Need to share browser instance and track concurrent requests across all handlers
27
+ - **Solution**: Global variables (`global.browser`, `global.browserLength`, `global.browserLimit`)
28
+ - **Rationale**: Browser instance is expensive to create; rate limiting requires shared state
29
+ - **Cons**: Not horizontally scalable; state is lost on restart
30
+
31
+ ## Browser Automation
32
+
33
+ **Browser Management**: Puppeteer with automatic reconnection
34
+ - **Problem**: Headless browsers can crash or disconnect unpredictably
35
+ - **Solution**: Auto-reconnection logic in `createBrowser.js` with exponential backoff
36
+ - **Technology**: `puppeteer-real-browser` package for enhanced stealth capabilities
37
+ - **Features**: Turnstile-ready configuration, XVFB support for headless environments, real browser fingerprinting
38
+
39
+ **Context Isolation**: Browser contexts per request
40
+ - **Problem**: Need to isolate each request's cookies, sessions, and proxy settings
41
+ - **Solution**: Create new browser context for each request with independent proxy configuration
42
+ - **Pros**: Complete isolation between concurrent requests; clean state per request
43
+ - **Cons**: Higher resource usage than tab-based isolation
44
+
45
+ **Timeout Management**: Per-request timeout with cleanup
46
+ - **Problem**: Browser automation can hang indefinitely on problematic sites
47
+ - **Solution**: Configurable timeout (`global.timeOut`) with context cleanup in all endpoints
48
+ - **Rationale**: Prevents resource leaks and ensures predictable response times
49
+
50
+ ## API Endpoints & Modes
51
+
52
+ **Mode-Based Architecture**: Six distinct bypass strategies
53
+ 1. **source**: Fetch page HTML after Cloudflare challenge resolution
54
+ 2. **turnstile-min**: Solve Turnstile CAPTCHA using injected fake page with real site key
55
+ 3. **turnstile-max**: Solve Turnstile on actual target page
56
+ 4. **waf-session**: Create authenticated session with Cloudflare cookies and headers
57
+ 5. **proxy-request**: Make authenticated requests using cookies/headers from waf-session (session reuse)
58
+
59
+ **Session Reuse Workflow** (proxy-request):
60
+ - First call `waf-session` to get cookies + headers
61
+ - Pass those to `proxy-request` via `cookies` and `sessionHeaders` parameters
62
+ - Requests are made through the same browser (Chrome fingerprint), maintaining CF clearance
63
+
64
+ **Request Validation**: JSON Schema validation with AJV
65
+ - **Problem**: Need to validate complex nested request structures (proxy configs, cookies, headers)
66
+ - **Solution**: AJV with format validation for URIs and enums for modes/HTTP methods
67
+ - **Rationale**: Schema-based validation provides clear error messages and type safety
68
+
69
+ ## Security & Rate Limiting
70
+
71
+ **Authentication**: Optional token-based authentication
72
+ - **Implementation**: `authToken` environment variable compared against request parameter
73
+ - **Design**: Simple bearer-style token authentication
74
+ - **Rationale**: Lightweight protection for self-hosted deployments
75
+
76
+ **Rate Limiting**: Browser instance concurrency limits
77
+ - **Problem**: Too many concurrent browser contexts can exhaust system resources
78
+ - **Solution**: `browserLimit` (default 20) enforced via `browserLength` counter
79
+ - **Mechanism**: Request rejected with 429 if limit exceeded
80
+ - **Cons**: In-memory counter doesn't work across multiple instances
81
+
82
+ **Proxy Authentication**: Built-in proxy credential handling
83
+ - **Problem**: Many proxies require username/password authentication
84
+ - **Solution**: Puppeteer's `page.authenticate()` for proxy credentials
85
+ - **Support**: Configurable per-request proxy with optional authentication
86
+
87
+ ## Environment Configuration
88
+
89
+ **Configuration Strategy**: Environment variables with sensible defaults
90
+ - `PORT`: Server port (default: 3939)
91
+ - `authToken`: Optional API authentication
92
+ - `browserLimit`: Max concurrent browser contexts (default: 20)
93
+ - `timeOut`: Request timeout in milliseconds (default: 60000)
94
+ - `SKIP_LAUNCH`: Skip browser initialization for testing
95
+ - `NODE_ENV`: Environment mode (development vs production)
96
+
97
+ # External Dependencies
98
+
99
+ ## Core Dependencies
100
+
101
+ **puppeteer-real-browser** (v1.4.0)
102
+ - **Purpose**: Enhanced Puppeteer wrapper with anti-detection features
103
+ - **Key Features**: Turnstile challenge solving, real browser fingerprinting, XVFB support
104
+ - **Integration**: Global browser instance managed in `module/createBrowser.js`
105
+
106
+ **Express** (v4.21.0)
107
+ - **Purpose**: HTTP server framework
108
+ - **Integration**: Main application server with middleware stack
109
+
110
+ **body-parser** (v1.20.3)
111
+ - **Purpose**: Parse JSON and URL-encoded request bodies
112
+ - **Integration**: Middleware for POST request handling
113
+
114
+ **cors** (v2.8.5)
115
+ - **Purpose**: Enable Cross-Origin Resource Sharing
116
+ - **Integration**: Middleware to allow API access from web clients
117
+
118
+ ## Validation & Schema
119
+
120
+ **ajv** (v8.17.1) + **ajv-formats** (v3.0.1)
121
+ - **Purpose**: JSON Schema validation with format extensions
122
+ - **Integration**: Request parameter validation in `module/reqValidate.js`
123
+ - **Schemas**: Validates URL formats, proxy configs, HTTP methods, cookie structures
124
+
125
+ ## Testing
126
+
127
+ **jest** (v29.7.0)
128
+ - **Purpose**: Test framework
129
+ - **Configuration**: Tests located in `tests/` directory with verbose output
130
+
131
+ **supertest** (v7.0.0)
132
+ - **Purpose**: HTTP assertion library for API testing
133
+ - **Integration**: Testing Express endpoints
134
+
135
+ ## External Services
136
+
137
+ **Cloudflare Turnstile API**
138
+ - **Integration**: Loaded via CDN script in fake page template
139
+ - **URL**: `https://challenges.cloudflare.com/turnstile/v0/api.js`
140
+ - **Purpose**: CAPTCHA challenge rendering and token generation
141
+
142
+ **httpbin.org**
143
+ - **Purpose**: Header detection service used in `wafSession.js` to extract Accept-Language header
144
+ - **Endpoint**: `https://httpbin.org/get`
145
+ - **Usage**: Validate browser fingerprinting fidelity
test-autz-gmail.js ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { connect } = require('puppeteer-real-browser');
2
+
3
+ async function createAutzAccountWithGmail() {
4
+ console.log('Launching browser...');
5
+
6
+ const { browser, page } = await connect({
7
+ headless: 'auto',
8
+ args: ['--no-sandbox', '--disable-setuid-sandbox'],
9
+ turnstile: true
10
+ });
11
+
12
+ const testEmail = 'benneu40@gmail.com';
13
+
14
+ try {
15
+ console.log('\n=== STEP 1: Navigate to Autz.org ===');
16
+ await page.goto('https://autz.org/onboarding/qinw2ix?callback_url=https%3A%2F%2Fmy.zone.id%2F', {
17
+ waitUntil: 'networkidle2',
18
+ timeout: 60000
19
+ });
20
+
21
+ console.log('Page loaded, waiting for content...');
22
+ await new Promise(resolve => setTimeout(resolve, 3000));
23
+
24
+ await page.screenshot({ path: 'gmail-step1.png', fullPage: true });
25
+ console.log('Screenshot saved: gmail-step1.png');
26
+
27
+ console.log('\n=== STEP 2: Enter Gmail address ===');
28
+ const emailField = await page.$('input[type="email"]');
29
+ if (emailField) {
30
+ console.log(`Entering email: ${testEmail}`);
31
+ await emailField.click();
32
+ await new Promise(resolve => setTimeout(resolve, 500));
33
+ await emailField.evaluate(el => el.value = '');
34
+ await emailField.type(testEmail, { delay: 100 });
35
+ await new Promise(resolve => setTimeout(resolve, 500));
36
+
37
+ const enteredValue = await emailField.evaluate(el => el.value);
38
+ console.log('Email field value:', enteredValue);
39
+ }
40
+
41
+ console.log('\n=== STEP 3: Wait for Turnstile captcha ===');
42
+ console.log('Waiting for captcha to auto-solve...');
43
+
44
+ let captchaSolved = false;
45
+ for (let i = 0; i < 30; i++) {
46
+ await new Promise(resolve => setTimeout(resolve, 1000));
47
+ const html = await page.content();
48
+
49
+ if (html.includes('Success') || html.includes('success')) {
50
+ console.log('Captcha solved!');
51
+ captchaSolved = true;
52
+ await new Promise(resolve => setTimeout(resolve, 2000));
53
+ break;
54
+ }
55
+
56
+ if (i % 5 === 0) {
57
+ console.log(`Waiting for captcha... ${i + 1}/30`);
58
+ }
59
+ }
60
+
61
+ await page.screenshot({ path: 'gmail-step2.png', fullPage: true });
62
+ console.log('Screenshot saved: gmail-step2.png');
63
+
64
+ console.log('\n=== STEP 4: Click Continue button ===');
65
+ const allBtns = await page.$$('button');
66
+ for (const btn of allBtns) {
67
+ const text = await page.evaluate(el => el.textContent, btn);
68
+ if (text && text.includes('Continue') && !text.includes('Google')) {
69
+ console.log('Clicking Continue button...');
70
+
71
+ await btn.focus();
72
+ await new Promise(resolve => setTimeout(resolve, 300));
73
+ await btn.click();
74
+ console.log('Clicked via .click()');
75
+
76
+ await new Promise(resolve => setTimeout(resolve, 1000));
77
+
78
+ await page.keyboard.press('Enter');
79
+ console.log('Pressed Enter key');
80
+ break;
81
+ }
82
+ }
83
+
84
+ console.log('Waiting for page to change...');
85
+ for (let i = 0; i < 15; i++) {
86
+ await new Promise(resolve => setTimeout(resolve, 1000));
87
+ const text = await page.evaluate(() => document.body.innerText);
88
+ if (text.includes('Password') || text.includes('password') || text.includes('code') || text.includes('verify')) {
89
+ console.log('Page changed! New content detected.');
90
+ break;
91
+ }
92
+ console.log(`Waiting... ${i + 1}/15`);
93
+ }
94
+
95
+ await page.screenshot({ path: 'gmail-step3.png', fullPage: true });
96
+ console.log('Screenshot saved: gmail-step3.png');
97
+
98
+ const pageText = await page.evaluate(() => document.body.innerText);
99
+ console.log('\n--- Current page content ---');
100
+ console.log(pageText.substring(0, 1500));
101
+
102
+ console.log('\nCurrent URL:', page.url());
103
+
104
+ if (pageText.includes('Password') && pageText.includes('Name')) {
105
+ console.log('\n=== SUCCESS: Registration form detected! ===');
106
+ console.log('The Gmail address worked - temp email domains were likely being blocked.');
107
+ } else if (pageText.includes('code') || pageText.includes('verify')) {
108
+ console.log('\n=== Verification step detected ===');
109
+ } else {
110
+ console.log('\n=== Page did not advance ===');
111
+ }
112
+
113
+ } catch (error) {
114
+ console.error('Error:', error.message);
115
+ await page.screenshot({ path: 'gmail-error.png', fullPage: true });
116
+ } finally {
117
+ await browser.close();
118
+ console.log('\nBrowser closed.');
119
+ }
120
+ }
121
+
122
+ createAutzAccountWithGmail();
test-autz-signup.js ADDED
@@ -0,0 +1,292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { connect } = require('puppeteer-real-browser');
2
+
3
+ async function createAutzAccount() {
4
+ console.log('Launching browser...');
5
+
6
+ const { browser, page } = await connect({
7
+ headless: 'auto',
8
+ args: ['--no-sandbox', '--disable-setuid-sandbox'],
9
+ turnstile: true
10
+ });
11
+
12
+ let tempEmail = null;
13
+ let tmailorPage = null;
14
+
15
+ try {
16
+ console.log('\n=== STEP 1: Get Temp Email from tmailor.com ===');
17
+ await page.goto('https://tmailor.com', {
18
+ waitUntil: 'networkidle2',
19
+ timeout: 60000
20
+ });
21
+ await new Promise(resolve => setTimeout(resolve, 3000));
22
+
23
+ const buttons = await page.$$('button, a');
24
+ for (const btn of buttons) {
25
+ const text = await page.evaluate(el => el.textContent, btn);
26
+ if (text && text.includes('New Email')) {
27
+ console.log('Clicking New Email button...');
28
+ await btn.click();
29
+ await new Promise(resolve => setTimeout(resolve, 3000));
30
+ break;
31
+ }
32
+ }
33
+
34
+ const emailInput = await page.$('input[readonly]');
35
+ if (emailInput) {
36
+ tempEmail = await page.evaluate(el => el.value, emailInput);
37
+ console.log('Got temp email:', tempEmail);
38
+ }
39
+
40
+ if (!tempEmail || !tempEmail.includes('@')) {
41
+ throw new Error('Could not get temp email from tmailor');
42
+ }
43
+
44
+ tmailorPage = page;
45
+
46
+ console.log('\n=== STEP 2: Navigate to Autz.org ===');
47
+ const autzPage = await browser.newPage();
48
+ await autzPage.goto('https://autz.org/onboarding/qinw2ix?callback_url=https%3A%2F%2Fmy.zone.id%2F', {
49
+ waitUntil: 'networkidle2',
50
+ timeout: 60000
51
+ });
52
+
53
+ console.log('Page loaded, waiting for content...');
54
+ await new Promise(resolve => setTimeout(resolve, 3000));
55
+
56
+ await autzPage.screenshot({ path: 'autz-step1.png', fullPage: true });
57
+ console.log('Screenshot saved: autz-step1.png');
58
+
59
+ const pageText = await autzPage.evaluate(() => document.body.innerText);
60
+ console.log('\n--- Page content (first 1000 chars) ---');
61
+ console.log(pageText.substring(0, 1000));
62
+
63
+ console.log('\n=== STEP 3: Look for email input field ===');
64
+ const emailSelectors = [
65
+ 'input[type="email"]',
66
+ 'input[name="email"]',
67
+ 'input[placeholder*="email" i]',
68
+ 'input[id*="email" i]',
69
+ 'input[type="text"]'
70
+ ];
71
+
72
+ let emailField = null;
73
+ for (const selector of emailSelectors) {
74
+ emailField = await autzPage.$(selector);
75
+ if (emailField) {
76
+ console.log(`Found email field with selector: ${selector}`);
77
+ break;
78
+ }
79
+ }
80
+
81
+ if (emailField) {
82
+ console.log(`Entering email: ${tempEmail}`);
83
+ await emailField.click();
84
+ await emailField.type(tempEmail, { delay: 50 });
85
+ await new Promise(resolve => setTimeout(resolve, 1000));
86
+ } else {
87
+ console.log('No email field found on page');
88
+ }
89
+
90
+ console.log('\n=== STEP 4: Wait for Turnstile captcha ===');
91
+
92
+ console.log('Looking for Turnstile iframe...');
93
+ await new Promise(resolve => setTimeout(resolve, 2000));
94
+
95
+ const frames = autzPage.frames();
96
+ console.log(`Found ${frames.length} frames`);
97
+ for (const frame of frames) {
98
+ const url = frame.url();
99
+ console.log('Frame:', url);
100
+ if (url.includes('turnstile') || url.includes('challenges')) {
101
+ console.log('Found Turnstile frame! Waiting for it to solve...');
102
+ await new Promise(resolve => setTimeout(resolve, 5000));
103
+ }
104
+ }
105
+
106
+ console.log('Waiting for captcha to auto-solve...');
107
+ let captchaSolved = false;
108
+ for (let i = 0; i < 30; i++) {
109
+ await new Promise(resolve => setTimeout(resolve, 1000));
110
+
111
+ const html = await autzPage.content();
112
+
113
+ if (html.includes('Success') || html.includes('success') || html.includes('cf-turnstile-success')) {
114
+ console.log('Captcha solved!');
115
+ captchaSolved = true;
116
+ await new Promise(resolve => setTimeout(resolve, 2000));
117
+ break;
118
+ }
119
+
120
+ if (i % 5 === 0) {
121
+ console.log(`Waiting for captcha... ${i + 1}/30`);
122
+ }
123
+ }
124
+
125
+ if (!captchaSolved) {
126
+ console.log('Captcha may not be fully solved, trying anyway...');
127
+ }
128
+
129
+ await autzPage.screenshot({ path: 'autz-step2.png', fullPage: true });
130
+ console.log('Screenshot saved: autz-step2.png');
131
+
132
+ console.log('\n=== STEP 5: Click Continue button ===');
133
+
134
+ const continueBtn = await autzPage.$('button');
135
+ const allContinueBtns = await autzPage.$$('button');
136
+
137
+ for (const btn of allContinueBtns) {
138
+ const text = await autzPage.evaluate(el => el.textContent, btn);
139
+ if (text && text.includes('Continue')) {
140
+ console.log('Found Continue button, clicking with mouse simulation...');
141
+
142
+ const box = await btn.boundingBox();
143
+ if (box) {
144
+ await autzPage.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
145
+ console.log('Clicked at', box.x + box.width / 2, box.y + box.height / 2);
146
+ } else {
147
+ await btn.click();
148
+ }
149
+ break;
150
+ }
151
+ }
152
+
153
+ console.log('Waiting for page content to change...');
154
+ for (let i = 0; i < 10; i++) {
155
+ await new Promise(resolve => setTimeout(resolve, 1000));
156
+ const text = await autzPage.evaluate(() => document.body.innerText);
157
+ if (text.includes('Password') || text.includes('password')) {
158
+ console.log('Registration form detected!');
159
+ break;
160
+ }
161
+ console.log(`Waiting... ${i + 1}/10`);
162
+ }
163
+
164
+ await new Promise(resolve => setTimeout(resolve, 2000));
165
+ await autzPage.screenshot({ path: 'autz-step3.png', fullPage: true });
166
+ console.log('Screenshot saved: autz-step3.png');
167
+
168
+ let pageText2 = await autzPage.evaluate(() => document.body.innerText);
169
+ console.log('\n--- Page after Continue (first 1000 chars) ---');
170
+ console.log(pageText2.substring(0, 1000));
171
+
172
+ const currentUrl = autzPage.url();
173
+ console.log('Current URL:', currentUrl);
174
+
175
+ if (pageText2.toLowerCase().includes('password') && pageText2.toLowerCase().includes('name')) {
176
+ console.log('\n=== STEP 6: Fill out registration form ===');
177
+
178
+ const passwordField = await autzPage.$('input[type="password"], input[name="password"], input[placeholder*="password" i]');
179
+ if (passwordField) {
180
+ const password = 'TestPass123!';
181
+ console.log('Entering password...');
182
+ await passwordField.click();
183
+ await passwordField.type(password, { delay: 30 });
184
+ }
185
+
186
+ const allInputs = await autzPage.$$('input');
187
+ console.log(`Found ${allInputs.length} input fields`);
188
+
189
+ for (const input of allInputs) {
190
+ const inputType = await autzPage.evaluate(el => el.type, input);
191
+ const inputName = await autzPage.evaluate(el => el.name || el.placeholder || '', input);
192
+ const inputValue = await autzPage.evaluate(el => el.value, input);
193
+ console.log(`Input: type=${inputType}, name/placeholder=${inputName}, value=${inputValue?.substring(0, 20)}`);
194
+
195
+ if (inputName.toLowerCase().includes('name') && !inputName.toLowerCase().includes('email') && !inputValue) {
196
+ console.log('Entering name...');
197
+ await input.click();
198
+ await input.type('Test User', { delay: 30 });
199
+ }
200
+
201
+ if ((inputType === 'tel' || inputName.toLowerCase().includes('phone')) && !inputValue) {
202
+ console.log('Entering phone...');
203
+ await input.click();
204
+ await input.type('+12025551234', { delay: 30 });
205
+ }
206
+ }
207
+
208
+ await autzPage.screenshot({ path: 'autz-step4-form.png', fullPage: true });
209
+ console.log('Screenshot saved: autz-step4-form.png');
210
+
211
+ console.log('Looking for Create Account button...');
212
+ const createBtn = await autzPage.$('button');
213
+ const allBtns = await autzPage.$$('button');
214
+ for (const btn of allBtns) {
215
+ const text = await autzPage.evaluate(el => el.textContent, btn);
216
+ if (text && (text.includes('Create Account') || text.includes('Register') || text.includes('Sign Up'))) {
217
+ console.log('Clicking Create Account button...');
218
+ await btn.click();
219
+ await new Promise(resolve => setTimeout(resolve, 5000));
220
+ break;
221
+ }
222
+ }
223
+
224
+ await autzPage.screenshot({ path: 'autz-step5-after-register.png', fullPage: true });
225
+ console.log('Screenshot saved: autz-step5-after-register.png');
226
+
227
+ const finalText = await autzPage.evaluate(() => document.body.innerText);
228
+ console.log('\n--- Page after registration (first 1000 chars) ---');
229
+ console.log(finalText.substring(0, 1000));
230
+ }
231
+
232
+ if (pageText2.toLowerCase().includes('code') || pageText2.toLowerCase().includes('verify') || pageText2.toLowerCase().includes('otp') || pageText2.toLowerCase().includes('sent') || pageText2.toLowerCase().includes('check your email')) {
233
+ console.log('\nVerification code requested! Need to check email...');
234
+ }
235
+
236
+ console.log('\n=== STEP 7: Check tmailor inbox for verification email ===');
237
+ await tmailorPage.bringToFront();
238
+
239
+ for (let attempt = 1; attempt <= 5; attempt++) {
240
+ console.log(`\nChecking inbox, attempt ${attempt}/5...`);
241
+
242
+ const refreshBtns = await tmailorPage.$$('button, a');
243
+ for (const btn of refreshBtns) {
244
+ const text = await tmailorPage.evaluate(el => el.textContent, btn);
245
+ if (text && (text.includes('Refresh') || text.includes('refresh'))) {
246
+ await btn.click();
247
+ await new Promise(resolve => setTimeout(resolve, 3000));
248
+ break;
249
+ }
250
+ }
251
+
252
+ const inboxText = await tmailorPage.evaluate(() => document.body.innerText);
253
+ if (inboxText.toLowerCase().includes('autz') ||
254
+ inboxText.toLowerCase().includes('verify') ||
255
+ inboxText.toLowerCase().includes('zone')) {
256
+ console.log('Found verification email!');
257
+
258
+ const emailLinks = await tmailorPage.$$('a, tr, div[class*="mail"], div[class*="message"]');
259
+ for (const link of emailLinks) {
260
+ const linkText = await tmailorPage.evaluate(el => el.textContent, link);
261
+ if (linkText && (linkText.toLowerCase().includes('autz') ||
262
+ linkText.toLowerCase().includes('verify') ||
263
+ linkText.toLowerCase().includes('zone'))) {
264
+ console.log('Clicking on email...');
265
+ await link.click();
266
+ await new Promise(resolve => setTimeout(resolve, 3000));
267
+ break;
268
+ }
269
+ }
270
+ break;
271
+ }
272
+
273
+ await new Promise(resolve => setTimeout(resolve, 5000));
274
+ }
275
+
276
+ await tmailorPage.screenshot({ path: 'tmailor-inbox.png', fullPage: true });
277
+ console.log('Screenshot saved: tmailor-inbox.png');
278
+
279
+ console.log('\n=== FINAL STATUS ===');
280
+ console.log('Temp Email Used:', tempEmail);
281
+ console.log('Check the screenshots to see the current state');
282
+
283
+ } catch (error) {
284
+ console.error('Error:', error.message);
285
+ await page.screenshot({ path: 'error-screenshot.png', fullPage: true });
286
+ } finally {
287
+ await browser.close();
288
+ console.log('\nBrowser closed.');
289
+ }
290
+ }
291
+
292
+ createAutzAccount();
test-tempmail.js ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { connect } = require('puppeteer-real-browser');
2
+
3
+ async function getTempMail() {
4
+ console.log('Launching browser...');
5
+
6
+ const { browser, page } = await connect({
7
+ headless: 'auto',
8
+ args: ['--no-sandbox', '--disable-setuid-sandbox'],
9
+ turnstile: true
10
+ });
11
+
12
+ try {
13
+ console.log('Navigating to tmailor.com...');
14
+ await page.goto('https://tmailor.com', {
15
+ waitUntil: 'networkidle2',
16
+ timeout: 60000
17
+ });
18
+
19
+ console.log('Initial page load complete. Waiting...');
20
+ await new Promise(resolve => setTimeout(resolve, 3000));
21
+
22
+ console.log('Looking for Turnstile iframe to solve...');
23
+
24
+ async function solveTurnstile() {
25
+ const frames = page.frames();
26
+ for (const frame of frames) {
27
+ const url = frame.url();
28
+ console.log('Frame URL:', url);
29
+ if (url.includes('turnstile') || url.includes('challenges.cloudflare')) {
30
+ console.log('Found Turnstile challenge frame!');
31
+ try {
32
+ await new Promise(resolve => setTimeout(resolve, 2000));
33
+ const checkbox = await frame.$('input[type="checkbox"]');
34
+ if (checkbox) {
35
+ console.log('Clicking Turnstile checkbox...');
36
+ await checkbox.click();
37
+ return true;
38
+ }
39
+ const verifyButton = await frame.$('[id*="verify"], [class*="verify"], button');
40
+ if (verifyButton) {
41
+ console.log('Clicking verify button in frame...');
42
+ await verifyButton.click();
43
+ return true;
44
+ }
45
+ } catch (e) {
46
+ console.log('Error in Turnstile frame:', e.message);
47
+ }
48
+ }
49
+ }
50
+ return false;
51
+ }
52
+
53
+ let turnstileSolved = await solveTurnstile();
54
+ if (turnstileSolved) {
55
+ console.log('Turnstile interaction attempted, waiting for verification...');
56
+ await new Promise(resolve => setTimeout(resolve, 8000));
57
+ }
58
+
59
+ console.log('Checking for Confirm link on page...');
60
+ const confirmButtons = await page.$$('a[title*="verify"], a[href*="firewall"], button');
61
+ for (const btn of confirmButtons) {
62
+ const text = await page.evaluate(el => el.textContent, btn);
63
+ if (text && text.toLowerCase().includes('confirm')) {
64
+ console.log('Found Confirm button, clicking...');
65
+ await btn.click();
66
+ await new Promise(resolve => setTimeout(resolve, 5000));
67
+
68
+ turnstileSolved = await solveTurnstile();
69
+ if (turnstileSolved) {
70
+ await new Promise(resolve => setTimeout(resolve, 8000));
71
+ }
72
+ break;
73
+ }
74
+ }
75
+
76
+ console.log('Looking for New Email button...');
77
+ const buttons = await page.$$('button, a');
78
+ for (const btn of buttons) {
79
+ const text = await page.evaluate(el => el.textContent, btn);
80
+ if (text && text.includes('New Email')) {
81
+ console.log('Found New Email button, clicking...');
82
+ await btn.click();
83
+ await new Promise(resolve => setTimeout(resolve, 3000));
84
+ break;
85
+ }
86
+ }
87
+
88
+ console.log('Waiting for email to appear...');
89
+ await new Promise(resolve => setTimeout(resolve, 5000));
90
+
91
+ let email = null;
92
+
93
+ console.log('Searching for email in page...');
94
+ const allText = await page.evaluate(() => document.body.innerText);
95
+
96
+ const emailPatterns = allText.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g);
97
+ if (emailPatterns) {
98
+ const filtered = emailPatterns.filter(e =>
99
+ !e.includes('gmail.com') &&
100
+ !e.includes('tmailor.com@') &&
101
+ !e.toLowerCase().includes('support')
102
+ );
103
+ if (filtered.length > 0) {
104
+ email = filtered[0];
105
+ console.log('Found email in page text:', email);
106
+ }
107
+ }
108
+
109
+ if (!email) {
110
+ const emailSelectors = [
111
+ '#email',
112
+ '#tempmail',
113
+ '.email-address',
114
+ '[data-email]',
115
+ 'input[readonly]',
116
+ 'input[type="text"]',
117
+ '.copy-text',
118
+ 'code',
119
+ 'pre'
120
+ ];
121
+
122
+ for (const selector of emailSelectors) {
123
+ try {
124
+ const elements = await page.$$(selector);
125
+ for (const element of elements) {
126
+ const value = await page.evaluate(el => {
127
+ return el.value || el.textContent || el.getAttribute('data-email') || el.getAttribute('data-clipboard-text');
128
+ }, element);
129
+ if (value && value.includes('@') && !value.includes('gmail')) {
130
+ email = value.trim();
131
+ console.log(`Found email with selector "${selector}": ${email}`);
132
+ break;
133
+ }
134
+ }
135
+ if (email) break;
136
+ } catch (e) {}
137
+ }
138
+ }
139
+
140
+ console.log('\nTaking screenshot...');
141
+ await page.screenshot({ path: 'tmailor-screenshot.png', fullPage: true });
142
+ console.log('Screenshot saved to tmailor-screenshot.png');
143
+
144
+ console.log('\n=== RESULT ===');
145
+ if (email && email.includes('@')) {
146
+ console.log('SUCCESS! Temp Email:', email);
147
+ } else {
148
+ console.log('Could not find a temp email address');
149
+ console.log('\n--- Page visible text (first 1500 chars) ---');
150
+ console.log(allText.substring(0, 1500));
151
+ }
152
+
153
+ return email;
154
+
155
+ } catch (error) {
156
+ console.error('Error:', error.message);
157
+ } finally {
158
+ await browser.close();
159
+ console.log('Browser closed.');
160
+ }
161
+ }
162
+
163
+ getTempMail();