lordofc commited on
Commit
1f72a3b
·
verified ·
1 Parent(s): b02912d

Upload 3 files

Browse files
Files changed (3) hide show
  1. lib/BypassService.js +233 -0
  2. lib/browser.js +270 -0
  3. lib/logger.js +33 -0
lib/BypassService.js ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { Logger } = require("./logger");
2
+ const { BrowserService } = require("./browser");
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ class BypassService {
7
+ constructor() {
8
+ this.logger = new Logger("Bypass");
9
+ this.browserService = new BrowserService();
10
+ this.fakePageContent = "";
11
+ this.loadFakePage();
12
+ }
13
+
14
+ loadFakePage() {
15
+ try {
16
+ let fakePagePath = path.join(process.cwd(), "assets", "fakePage.html");
17
+ if (!fs.existsSync(fakePagePath)) {
18
+ fakePagePath = path.join(process.cwd(), "fakePage.html");
19
+ }
20
+ const assetsDir = path.dirname(fakePagePath);
21
+ if (!fs.existsSync(assetsDir)) {
22
+ fs.mkdirSync(assetsDir, { recursive: true });
23
+ }
24
+ if (!fs.existsSync(fakePagePath)) {
25
+ this.fakePageContent = `<!DOCTYPE html>
26
+ <html lang="en">
27
+ <head>
28
+ <meta charset="UTF-8">
29
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
30
+ <title></title>
31
+ </head>
32
+ <body>
33
+ <div class="turnstile"></div>
34
+ <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback" defer></script>
35
+ <script>
36
+ window.onloadTurnstileCallback = function () {
37
+ turnstile.render('.turnstile', {
38
+ sitekey: '<site-key>',
39
+ callback: function (token) {
40
+ var c = document.createElement('input');
41
+ c.type = 'hidden';
42
+ c.name = 'cf-response';
43
+ c.value = token;
44
+ document.body.appendChild(c);
45
+ },
46
+ });
47
+ };
48
+ </script>
49
+ </body>
50
+ </html>`;
51
+ fs.writeFileSync(fakePagePath, this.fakePageContent);
52
+ } else {
53
+ this.fakePageContent = fs.readFileSync(fakePagePath, "utf-8");
54
+ }
55
+ this.logger.success("Fake page loaded successfully");
56
+ } catch (error) {
57
+ this.logger.error("Failed to load fake page:", error);
58
+ this.fakePageContent = `<!DOCTYPE html>
59
+ <html lang="en">
60
+ <head>
61
+ <meta charset="UTF-8">
62
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
63
+ <title></title>
64
+ </head>
65
+ <body>
66
+ <div class="turnstile"></div>
67
+ <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback" defer></script>
68
+ <script>
69
+ window.onloadTurnstileCallback = function () {
70
+ turnstile.render('.turnstile', {
71
+ sitekey: '<site-key>',
72
+ callback: function (token) {
73
+ var c = document.createElement('input');
74
+ c.type = 'hidden';
75
+ c.name = 'cf-response';
76
+ c.value = token;
77
+ document.body.appendChild(c);
78
+ },
79
+ });
80
+ };
81
+ </script>
82
+ </body>
83
+ </html>`;
84
+ }
85
+ }
86
+
87
+ async solveTurnstileMin(url, siteKey, proxy, timeout = 60000) {
88
+ const startTime = Date.now();
89
+ try {
90
+ if (!url || !siteKey) throw new Error("Missing url or siteKey parameter");
91
+ const token = await this.browserService.withBrowserContext(async (context) => {
92
+ const page = await context.newPage();
93
+ if (proxy?.username && proxy?.password) {
94
+ await page.authenticate({
95
+ username: proxy.username,
96
+ password: proxy.password,
97
+ });
98
+ }
99
+ await page.setRequestInterception(true);
100
+ page.on("request", async (request) => {
101
+ if ([url, url + "/"].includes(request.url()) && request.resourceType() === "document") {
102
+ await request.respond({
103
+ status: 200,
104
+ contentType: "text/html",
105
+ body: this.fakePageContent.replace(/<site-key>/g, siteKey),
106
+ });
107
+ } else {
108
+ await request.continue();
109
+ }
110
+ });
111
+ await page.goto(url, { waitUntil: "domcontentloaded" });
112
+ await page.waitForSelector('[name="cf-response"]', { timeout });
113
+ return page.evaluate(() => {
114
+ try {
115
+ const input = document.querySelector('[name="cf-response"]');
116
+ return input ? input.value : null;
117
+ } catch {
118
+ return null;
119
+ }
120
+ });
121
+ });
122
+ if (!token || token.length < 10) throw new Error("Failed to get token");
123
+ return {
124
+ success: true,
125
+ creator: "LoRDx",
126
+ data: token,
127
+ duration: Date.now() - startTime,
128
+ };
129
+ } catch (error) {
130
+ this.logger.error("Turnstile min solve error:", error);
131
+ return {
132
+ success: false,
133
+ error: error.message || "Unknown error",
134
+ duration: Date.now() - startTime,
135
+ };
136
+ }
137
+ }
138
+
139
+ async getSource(url, proxy, timeout = 60000) {
140
+ const startTime = Date.now();
141
+ try {
142
+ if (!url) throw new Error("Missing url parameter");
143
+ const result = await this.browserService.withBrowserContext(async (context) => {
144
+ const page = await context.newPage();
145
+ await page.setDefaultTimeout(30000);
146
+ await page.setDefaultNavigationTimeout(30000);
147
+ if (proxy?.username && proxy?.password) {
148
+ await page.authenticate({
149
+ username: proxy.username,
150
+ password: proxy.password,
151
+ });
152
+ }
153
+ await page.setRequestInterception(true);
154
+ let resolved = false;
155
+ return new Promise((resolve, reject) => {
156
+ const timeoutHandler = setTimeout(() => {
157
+ if (!resolved) {
158
+ resolved = true;
159
+ reject(new Error("Timeout Error"));
160
+ }
161
+ }, timeout);
162
+ page.on("request", async (request) => {
163
+ try {
164
+ await request.continue();
165
+ } catch { }
166
+ });
167
+
168
+ page.on("response", async (res) => {
169
+ try {
170
+ if (!resolved && [200, 302].includes(res.status()) && [url, url + "/"].includes(res.url())) {
171
+ await page.waitForNavigation({ waitUntil: "load", timeout: 5000 }).catch(() => { });
172
+ const html = await page.content();
173
+ resolved = true;
174
+ clearTimeout(timeoutHandler);
175
+ resolve(html);
176
+ }
177
+ } catch (error) {
178
+ if (!resolved) {
179
+ resolved = true;
180
+ clearTimeout(timeoutHandler);
181
+ reject(error);
182
+ }
183
+ }
184
+ });
185
+
186
+ page.goto(url, { waitUntil: "domcontentloaded", timeout: 30000 }).catch((error) => {
187
+ if (!resolved) {
188
+ resolved = true;
189
+ clearTimeout(timeoutHandler);
190
+ reject(error);
191
+ }
192
+ });
193
+ });
194
+ });
195
+
196
+ return {
197
+ success: true,
198
+ creator: "LoRDx",
199
+ data: result,
200
+ duration: Date.now() - startTime,
201
+ };
202
+ } catch (error) {
203
+ this.logger.error("Get source error:", error);
204
+ return {
205
+ success: false,
206
+ error: error.message || "Unknown error",
207
+ duration: Date.now() - startTime,
208
+ };
209
+ }
210
+ }
211
+
212
+ async findAcceptLanguage(page) {
213
+ try {
214
+ return await page.evaluate(async () => {
215
+ try {
216
+ const res = await fetch("https://httpbin.org/get");
217
+ const json = await res.json();
218
+ return json.headers["Accept-Language"] || json.headers["accept-language"] || null;
219
+ } catch {
220
+ return null;
221
+ }
222
+ });
223
+ } catch {
224
+ return "en-US,en;q=0.9";
225
+ }
226
+ }
227
+
228
+ getStats() {
229
+ return this.browserService.getBrowserStats();
230
+ }
231
+ }
232
+
233
+ module.exports = { BypassService };
lib/browser.js ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { Logger } = require("./logger");
2
+ const { connect } = require("puppeteer-real-browser");
3
+ const os = require("os");
4
+
5
+ class BrowserService {
6
+ constructor() {
7
+ this.logger = new Logger("Browser");
8
+ this.browser = null;
9
+ this.browserContexts = new Set();
10
+ this.cleanupTimer = null;
11
+ this.isShuttingDown = false;
12
+ this.reconnectAttempts = 0;
13
+ this.maxReconnectAttempts = 5;
14
+ this.cleanupInterval = 30000;
15
+ this.contextTimeout = 300000;
16
+ this.contextCreationTimes = new Map();
17
+ this.stats = {
18
+ totalContexts: 0,
19
+ activeContexts: 0,
20
+ memoryUsage: 0,
21
+ cpuUsage: 0,
22
+ lastCleanup: Date.now(),
23
+ };
24
+ const cpuCores = os.cpus().length;
25
+ this.contextLimit = Math.max(cpuCores * 4, 16);
26
+ this.logger.info(`Browser service initialized with context limit: ${this.contextLimit}`);
27
+ this.setupGracefulShutdown();
28
+ this.startPeriodicCleanup();
29
+ }
30
+ async initialize(options = {}) {
31
+ if (this.isShuttingDown) return;
32
+ try {
33
+ await this.closeBrowser();
34
+ this.logger.info("Launching browser...");
35
+ const defaultWidth = 1024;
36
+ const defaultHeight = 768;
37
+ const width = options.width || defaultWidth;
38
+ const height = options.height || defaultHeight;
39
+ const { browser } = await connect({
40
+ headless: false,
41
+ turnstile: true,
42
+ connectOption: {
43
+ defaultViewport: { width, height },
44
+ timeout: 120000,
45
+ protocolTimeout: 300000,
46
+ args: [
47
+ `--window-size=${width},${height}`,
48
+ "--no-sandbox",
49
+ "--disable-setuid-sandbox",
50
+ "--disable-dev-shm-usage",
51
+ "--disable-gpu",
52
+ "--no-first-run",
53
+ "--disable-extensions",
54
+ "--disable-sync",
55
+ "--disable-translate",
56
+ "--disable-web-security",
57
+ "--disable-features=VizDisplayCompositor",
58
+ ],
59
+ },
60
+ disableXvfb: false,
61
+ });
62
+ if (!browser) throw new Error("Failed to connect to browser");
63
+ this.browser = browser;
64
+ this.reconnectAttempts = 0;
65
+ this.setupBrowserEventHandlers();
66
+ this.wrapBrowserMethods();
67
+ this.logger.success("Browser launched successfully");
68
+ } catch (error) {
69
+ this.logger.error("Browser initialization failed:", error);
70
+ if (this.reconnectAttempts < this.maxReconnectAttempts && !this.isShuttingDown) {
71
+ this.reconnectAttempts++;
72
+ this.logger.warn(`Retrying browser initialization (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
73
+ await new Promise((resolve) => setTimeout(resolve, 5000 * this.reconnectAttempts));
74
+ return this.initialize(options);
75
+ }
76
+ throw error;
77
+ }
78
+ }
79
+
80
+ setupBrowserEventHandlers() {
81
+ if (!this.browser) return;
82
+ this.browser.on("disconnected", async () => {
83
+ if (this.isShuttingDown) return;
84
+ this.logger.warn("Browser disconnected, attempting to reconnect...");
85
+ await this.handleBrowserDisconnection();
86
+ });
87
+ this.browser.on("targetcreated", () => this.updateStats());
88
+ this.browser.on("targetdestroyed", () => this.updateStats());
89
+ }
90
+
91
+ wrapBrowserMethods() {
92
+ if (!this.browser) return;
93
+ const originalCreateContext = this.browser.createBrowserContext.bind(this.browser);
94
+ this.browser.createBrowserContext = async (...args) => {
95
+ if (this.browserContexts.size >= this.contextLimit) {
96
+ await this.forceCleanupOldContexts();
97
+ if (this.browserContexts.size >= this.contextLimit) {
98
+ throw new Error(`Browser context limit reached (${this.contextLimit})`);
99
+ }
100
+ }
101
+ const context = await originalCreateContext(...args);
102
+ if (context) {
103
+ this.browserContexts.add(context);
104
+ this.contextCreationTimes.set(context, Date.now());
105
+ this.stats.totalContexts++;
106
+ const originalClose = context.close.bind(context);
107
+ context.close = async () => {
108
+ try {
109
+ await originalClose();
110
+ } catch (error) {
111
+ this.logger.warn("Error closing context:", error.message);
112
+ } finally {
113
+ this.browserContexts.delete(context);
114
+ this.contextCreationTimes.delete(context);
115
+ this.updateStats();
116
+ }
117
+ };
118
+
119
+ setTimeout(async () => {
120
+ if (this.browserContexts.has(context)) {
121
+ this.logger.debug("Force closing expired context");
122
+ try {
123
+ await context.close();
124
+ } catch { }
125
+ }
126
+ }, this.contextTimeout);
127
+ }
128
+ this.updateStats();
129
+ return context;
130
+ };
131
+ }
132
+
133
+ async handleBrowserDisconnection() {
134
+ try {
135
+ const cleanupPromises = Array.from(this.browserContexts).map((context) => context.close().catch(() => { }));
136
+ await Promise.allSettled(cleanupPromises);
137
+ this.browserContexts.clear();
138
+ this.contextCreationTimes.clear();
139
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
140
+ await new Promise((resolve) => setTimeout(resolve, 5000));
141
+ await this.initialize();
142
+ } else {
143
+ this.logger.error("Max reconnection attempts reached");
144
+ }
145
+ } catch (error) {
146
+ this.logger.error("Error handling browser disconnection:", error);
147
+ }
148
+ }
149
+
150
+ startPeriodicCleanup() {
151
+ this.cleanupTimer = setInterval(async () => {
152
+ if (this.isShuttingDown) return;
153
+ try {
154
+ await this.performCleanup();
155
+ this.updateStats();
156
+ } catch (error) {
157
+ this.logger.error("Periodic cleanup error:", error);
158
+ }
159
+ }, this.cleanupInterval);
160
+ }
161
+
162
+ async performCleanup() {
163
+ const now = Date.now();
164
+ const contextsToCleanup = [];
165
+ for (const [context, creationTime] of this.contextCreationTimes.entries()) {
166
+ if (now - creationTime > this.contextTimeout) {
167
+ contextsToCleanup.push(context);
168
+ }
169
+ }
170
+ if (contextsToCleanup.length > 0) {
171
+ this.logger.debug(`Cleaning up ${contextsToCleanup.length} expired contexts`);
172
+ const cleanupPromises = contextsToCleanup.map((context) => context.close().catch(() => { }));
173
+ await Promise.allSettled(cleanupPromises);
174
+ }
175
+ if (this.browserContexts.size > this.contextLimit * 0.8) await this.forceCleanupOldContexts();
176
+ this.stats.lastCleanup = now;
177
+ }
178
+
179
+ async forceCleanupOldContexts() {
180
+ const contextsArray = Array.from(this.browserContexts);
181
+ const sortedContexts = contextsArray.sort((a, b) => {
182
+ const timeA = this.contextCreationTimes.get(a) || 0;
183
+ const timeB = this.contextCreationTimes.get(b) || 0;
184
+ return timeA - timeB;
185
+ });
186
+ const toCleanup = sortedContexts.slice(0, Math.floor(sortedContexts.length * 0.3));
187
+ if (toCleanup.length > 0) {
188
+ this.logger.warn(`Force cleaning up ${toCleanup.length} contexts due to limit`);
189
+ const cleanupPromises = toCleanup.map((context) => context.close().catch(() => { }));
190
+ await Promise.allSettled(cleanupPromises);
191
+ }
192
+ }
193
+
194
+ updateStats() {
195
+ this.stats.activeContexts = this.browserContexts.size;
196
+ this.stats.memoryUsage = process.memoryUsage().heapUsed;
197
+ const usage = process.cpuUsage();
198
+ this.stats.cpuUsage = (usage.user + usage.system) / 1000000;
199
+ }
200
+
201
+ async createContext(options = {}) {
202
+ if (!this.browser) await this.initialize();
203
+ if (!this.browser) throw new Error("Browser not available");
204
+ return await this.browser.createBrowserContext({
205
+ ...options,
206
+ ignoreHTTPSErrors: true,
207
+ });
208
+ }
209
+
210
+ async withBrowserContext(callback) {
211
+ let context = null;
212
+ try {
213
+ context = await this.createContext();
214
+ return await callback(context);
215
+ } finally {
216
+ if (context) {
217
+ try {
218
+ await context.close();
219
+ } catch (error) {
220
+ this.logger.warn(`Failed to close context: ${error.message}`);
221
+ }
222
+ }
223
+ }
224
+ }
225
+
226
+ getBrowserStats() {
227
+ return { ...this.stats };
228
+ }
229
+
230
+ isReady() {
231
+ return this.browser !== null && !this.isShuttingDown;
232
+ }
233
+
234
+ async closeBrowser() {
235
+ if (this.browser) {
236
+ try {
237
+ const cleanupPromises = Array.from(this.browserContexts).map((context) => context.close().catch(() => { }));
238
+ await Promise.allSettled(cleanupPromises);
239
+ this.browserContexts.clear();
240
+ this.contextCreationTimes.clear();
241
+ await this.browser.close();
242
+ this.logger.info("Browser closed successfully");
243
+ } catch (error) {
244
+ this.logger.error("Error closing browser:", error);
245
+ } finally {
246
+ this.browser = null;
247
+ }
248
+ }
249
+ }
250
+
251
+ setupGracefulShutdown() {
252
+ const gracefulShutdown = async (signal) => {
253
+ this.logger.warn(`Received ${signal}, shutting down browser service...`);
254
+ this.isShuttingDown = true;
255
+ if (this.cleanupTimer) clearInterval(this.cleanupTimer);
256
+ await this.closeBrowser();
257
+ this.logger.success("Browser service shutdown complete");
258
+ };
259
+ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
260
+ process.on("SIGINT", () => gracefulShutdown("SIGINT"));
261
+ }
262
+
263
+ async shutdown() {
264
+ this.isShuttingDown = true;
265
+ if (this.cleanupTimer) clearInterval(this.cleanupTimer);
266
+ await this.closeBrowser();
267
+ }
268
+ }
269
+
270
+ module.exports = { BrowserService };
lib/logger.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class Logger {
2
+ constructor(context) {
3
+ this.context = context;
4
+ }
5
+
6
+ getTimestamp() {
7
+ return new Date().toISOString().replace("T", " ").substring(0, 19);
8
+ }
9
+
10
+ info(message, ...args) {
11
+ console.log(`[INFO] [${this.getTimestamp()}] [${this.context}] | ${message}`, ...args);
12
+ }
13
+
14
+ warn(message, ...args) {
15
+ console.log(`[WARN] [${this.getTimestamp()}] [${this.context}] | ${message}`, ...args);
16
+ }
17
+
18
+ error(message, ...args) {
19
+ console.log(`[ERROR] [${this.getTimestamp()}] [${this.context}] | ${message}`, ...args);
20
+ }
21
+
22
+ success(message, ...args) {
23
+ console.log(`[SUCCESS] [${this.getTimestamp()}] [${this.context}] | ${message}`, ...args);
24
+ }
25
+
26
+ debug(message, ...args) {
27
+ if (process.env.NODE_ENV === "development") {
28
+ console.log(`[DEBUG] [${this.getTimestamp()}] [${this.context}] | ${message}`, ...args);
29
+ }
30
+ }
31
+ }
32
+
33
+ module.exports = { Logger };