@fanboynz/network-scanner 1.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/npm-publish.yml +33 -0
- package/JSONMANUAL.md +121 -0
- package/LICENSE +674 -0
- package/README.md +357 -0
- package/config.json +74 -0
- package/lib/browserexit.js +522 -0
- package/lib/browserhealth.js +308 -0
- package/lib/cloudflare.js +660 -0
- package/lib/colorize.js +168 -0
- package/lib/compare.js +159 -0
- package/lib/compress.js +129 -0
- package/lib/fingerprint.js +613 -0
- package/lib/flowproxy.js +274 -0
- package/lib/grep.js +348 -0
- package/lib/ignore_similar.js +237 -0
- package/lib/nettools.js +1200 -0
- package/lib/output.js +633 -0
- package/lib/redirect.js +384 -0
- package/lib/searchstring.js +561 -0
- package/lib/validate_rules.js +1107 -0
- package/nwss.1 +824 -0
- package/nwss.js +2488 -0
- package/package.json +45 -0
- package/regex-samples.md +27 -0
- package/scanner-script-org.js +588 -0
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare bypass and challenge handling module - Updated for 2025
|
|
3
|
+
* Handles phishing warnings, Turnstile challenges, and modern Cloudflare protections
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Cross-version compatible timeout function for Puppeteer
|
|
8
|
+
* @param {import('puppeteer').Page} page - Puppeteer page instance
|
|
9
|
+
* @param {number} timeout - Timeout in milliseconds
|
|
10
|
+
* @returns {Promise<void>}
|
|
11
|
+
*/
|
|
12
|
+
async function waitForTimeout(page, timeout) {
|
|
13
|
+
try {
|
|
14
|
+
// Try newer Puppeteer method first
|
|
15
|
+
if (typeof page.waitForTimeout === 'function') {
|
|
16
|
+
await page.waitForTimeout(timeout);
|
|
17
|
+
} else if (typeof page.waitFor === 'function') {
|
|
18
|
+
// Fallback to older Puppeteer method
|
|
19
|
+
await page.waitFor(timeout);
|
|
20
|
+
} else {
|
|
21
|
+
// Ultimate fallback using setTimeout
|
|
22
|
+
await new Promise(resolve => setTimeout(resolve, timeout));
|
|
23
|
+
}
|
|
24
|
+
} catch (error) {
|
|
25
|
+
// If all else fails, use setTimeout
|
|
26
|
+
await new Promise(resolve => setTimeout(resolve, timeout));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Analyzes the current page to detect Cloudflare challenges - Updated for 2025
|
|
32
|
+
* @param {import('puppeteer').Page} page - Puppeteer page instance
|
|
33
|
+
* @returns {Promise<object>} Challenge information object
|
|
34
|
+
*/
|
|
35
|
+
async function analyzeCloudflareChallenge(page) {
|
|
36
|
+
try {
|
|
37
|
+
return await page.evaluate(() => {
|
|
38
|
+
const title = document.title || '';
|
|
39
|
+
const bodyText = document.body ? document.body.textContent : '';
|
|
40
|
+
|
|
41
|
+
// Updated selectors for 2025 Cloudflare challenges
|
|
42
|
+
const hasTurnstileIframe = document.querySelector('iframe[title*="Cloudflare security challenge"]') !== null ||
|
|
43
|
+
document.querySelector('iframe[src*="challenges.cloudflare.com"]') !== null ||
|
|
44
|
+
document.querySelector('iframe[title*="Widget containing a Cloudflare"]') !== null;
|
|
45
|
+
|
|
46
|
+
const hasTurnstileContainer = document.querySelector('.cf-turnstile') !== null ||
|
|
47
|
+
document.querySelector('.ctp-checkbox-container') !== null ||
|
|
48
|
+
document.querySelector('.ctp-checkbox-label') !== null;
|
|
49
|
+
|
|
50
|
+
const hasTurnstileCheckbox = document.querySelector('input[type="checkbox"].ctp-checkbox') !== null ||
|
|
51
|
+
document.querySelector('.ctp-checkbox') !== null;
|
|
52
|
+
|
|
53
|
+
// Legacy challenge detection (still used on some sites)
|
|
54
|
+
const hasLegacyCheckbox = document.querySelector('input[type="checkbox"]#challenge-form') !== null ||
|
|
55
|
+
document.querySelector('input[type="checkbox"][name="cf_captcha_kind"]') !== null;
|
|
56
|
+
|
|
57
|
+
const hasChallengeRunning = document.querySelector('.cf-challenge-running') !== null ||
|
|
58
|
+
document.querySelector('.cf-challenge-container') !== null ||
|
|
59
|
+
document.querySelector('.challenge-stage') !== null ||
|
|
60
|
+
document.querySelector('.challenge-form') !== null;
|
|
61
|
+
|
|
62
|
+
const hasDataRay = document.querySelector('[data-ray]') !== null ||
|
|
63
|
+
document.querySelector('[data-cf-challenge]') !== null;
|
|
64
|
+
|
|
65
|
+
const hasCaptcha = bodyText.includes('CAPTCHA') || bodyText.includes('captcha') ||
|
|
66
|
+
bodyText.includes('hCaptcha') || bodyText.includes('reCAPTCHA');
|
|
67
|
+
|
|
68
|
+
const hasJSChallenge = document.querySelector('script[src*="/cdn-cgi/challenge-platform/"]') !== null ||
|
|
69
|
+
bodyText.includes('Checking your browser') ||
|
|
70
|
+
bodyText.includes('Please wait while we verify');
|
|
71
|
+
|
|
72
|
+
const hasPhishingWarning = bodyText.includes('This website has been reported for potential phishing') ||
|
|
73
|
+
title.includes('Attention Required') ||
|
|
74
|
+
document.querySelector('a[href*="continue"]') !== null;
|
|
75
|
+
|
|
76
|
+
// Check for Turnstile response token
|
|
77
|
+
const hasTurnstileResponse = document.querySelector('input[name="cf-turnstile-response"]') !== null;
|
|
78
|
+
|
|
79
|
+
// Check for challenge completion indicators
|
|
80
|
+
const isChallengeCompleted = hasTurnstileResponse &&
|
|
81
|
+
document.querySelector('input[name="cf-turnstile-response"]')?.value;
|
|
82
|
+
|
|
83
|
+
// Enhanced challenge detection logic
|
|
84
|
+
const isChallengePresent = title.includes('Just a moment') ||
|
|
85
|
+
title.includes('Checking your browser') ||
|
|
86
|
+
bodyText.includes('Verify you are human') ||
|
|
87
|
+
hasLegacyCheckbox ||
|
|
88
|
+
hasChallengeRunning ||
|
|
89
|
+
hasDataRay ||
|
|
90
|
+
hasTurnstileIframe ||
|
|
91
|
+
hasTurnstileContainer ||
|
|
92
|
+
hasJSChallenge;
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
isChallengePresent,
|
|
96
|
+
isPhishingWarning: hasPhishingWarning,
|
|
97
|
+
isTurnstile: hasTurnstileIframe || hasTurnstileContainer || hasTurnstileCheckbox,
|
|
98
|
+
isJSChallenge: hasJSChallenge,
|
|
99
|
+
isChallengeCompleted,
|
|
100
|
+
title,
|
|
101
|
+
hasLegacyCheckbox,
|
|
102
|
+
hasTurnstileIframe,
|
|
103
|
+
hasTurnstileContainer,
|
|
104
|
+
hasTurnstileCheckbox,
|
|
105
|
+
hasChallengeRunning,
|
|
106
|
+
hasDataRay,
|
|
107
|
+
hasCaptcha,
|
|
108
|
+
hasTurnstileResponse,
|
|
109
|
+
url: window.location.href,
|
|
110
|
+
bodySnippet: bodyText.substring(0, 200) // First 200 chars for debugging
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
} catch (error) {
|
|
114
|
+
return {
|
|
115
|
+
isChallengePresent: false,
|
|
116
|
+
isPhishingWarning: false,
|
|
117
|
+
isTurnstile: false,
|
|
118
|
+
isJSChallenge: false,
|
|
119
|
+
isChallengeCompleted: false,
|
|
120
|
+
error: error.message
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Handles Cloudflare phishing warnings by clicking the continue button
|
|
127
|
+
* @param {import('puppeteer').Page} page - Puppeteer page instance
|
|
128
|
+
* @param {string} currentUrl - Current URL being processed
|
|
129
|
+
* @param {boolean} forceDebug - Debug mode flag
|
|
130
|
+
* @returns {Promise<object>} Result object with success status and details
|
|
131
|
+
*/
|
|
132
|
+
async function handlePhishingWarning(page, currentUrl, forceDebug = false) {
|
|
133
|
+
const result = {
|
|
134
|
+
success: false,
|
|
135
|
+
attempted: false,
|
|
136
|
+
error: null,
|
|
137
|
+
details: null
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
if (forceDebug) console.log(`[debug][cloudflare] Checking for phishing warning on ${currentUrl}`);
|
|
142
|
+
|
|
143
|
+
// Wait a moment for the warning page to load
|
|
144
|
+
await waitForTimeout(page, 2000);
|
|
145
|
+
|
|
146
|
+
const challengeInfo = await analyzeCloudflareChallenge(page);
|
|
147
|
+
|
|
148
|
+
if (challengeInfo.isPhishingWarning) {
|
|
149
|
+
result.attempted = true;
|
|
150
|
+
result.details = challengeInfo;
|
|
151
|
+
|
|
152
|
+
if (forceDebug) {
|
|
153
|
+
console.log(`[debug][cloudflare] Phishing warning detected on ${currentUrl}:`);
|
|
154
|
+
console.log(`[debug][cloudflare] Page Title: "${challengeInfo.title}"`);
|
|
155
|
+
console.log(`[debug][cloudflare] Current URL: ${challengeInfo.url}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
await page.click('a[href*="continue"]', { timeout: 5000 });
|
|
160
|
+
await page.waitForNavigation({ waitUntil: 'load', timeout: 30000 });
|
|
161
|
+
|
|
162
|
+
result.success = true;
|
|
163
|
+
if (forceDebug) console.log(`[debug][cloudflare] Successfully bypassed phishing warning for ${currentUrl}`);
|
|
164
|
+
} catch (clickError) {
|
|
165
|
+
result.error = `Failed to click continue button: ${clickError.message}`;
|
|
166
|
+
if (forceDebug) console.log(`[debug][cloudflare] Failed to bypass phishing warning: ${clickError.message}`);
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
if (forceDebug) console.log(`[debug][cloudflare] No phishing warning detected on ${currentUrl}`);
|
|
170
|
+
result.success = true; // No warning to handle
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
result.error = error.message;
|
|
174
|
+
if (forceDebug) console.log(`[debug][cloudflare] Phishing warning check failed for ${currentUrl}: ${error.message}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Attempts to solve Cloudflare challenges including Turnstile - Updated for 2025
|
|
182
|
+
* @param {import('puppeteer').Page} page - Puppeteer page instance
|
|
183
|
+
* @param {string} currentUrl - Current URL being processed
|
|
184
|
+
* @param {boolean} forceDebug - Debug mode flag
|
|
185
|
+
* @returns {Promise<object>} Result object with success status and details
|
|
186
|
+
*/
|
|
187
|
+
async function handleVerificationChallenge(page, currentUrl, forceDebug = false) {
|
|
188
|
+
const result = {
|
|
189
|
+
success: false,
|
|
190
|
+
attempted: false,
|
|
191
|
+
error: null,
|
|
192
|
+
details: null,
|
|
193
|
+
requiresHuman: false
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
if (forceDebug) console.log(`[debug][cloudflare] Checking for verification challenge on ${currentUrl}`);
|
|
198
|
+
|
|
199
|
+
// Wait for potential Cloudflare challenge to appear
|
|
200
|
+
await waitForTimeout(page, 3000);
|
|
201
|
+
|
|
202
|
+
const challengeInfo = await analyzeCloudflareChallenge(page);
|
|
203
|
+
result.details = challengeInfo;
|
|
204
|
+
|
|
205
|
+
if (challengeInfo.isChallengePresent) {
|
|
206
|
+
result.attempted = true;
|
|
207
|
+
|
|
208
|
+
if (forceDebug) {
|
|
209
|
+
console.log(`[debug][cloudflare] Challenge detected on ${currentUrl}:`);
|
|
210
|
+
console.log(`[debug][cloudflare] Page Title: "${challengeInfo.title}"`);
|
|
211
|
+
console.log(`[debug][cloudflare] Current URL: ${challengeInfo.url}`);
|
|
212
|
+
console.log(`[debug][cloudflare] Is Turnstile: ${challengeInfo.isTurnstile}`);
|
|
213
|
+
console.log(`[debug][cloudflare] Is JS Challenge: ${challengeInfo.isJSChallenge}`);
|
|
214
|
+
console.log(`[debug][cloudflare] Has Legacy Checkbox: ${challengeInfo.hasLegacyCheckbox}`);
|
|
215
|
+
console.log(`[debug][cloudflare] Has Turnstile Iframe: ${challengeInfo.hasTurnstileIframe}`);
|
|
216
|
+
console.log(`[debug][cloudflare] Has Turnstile Container: ${challengeInfo.hasTurnstileContainer}`);
|
|
217
|
+
console.log(`[debug][cloudflare] Has CAPTCHA: ${challengeInfo.hasCaptcha}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Check for CAPTCHA that requires human intervention
|
|
221
|
+
if (challengeInfo.hasCaptcha) {
|
|
222
|
+
result.requiresHuman = true;
|
|
223
|
+
result.error = 'CAPTCHA detected - requires human intervention';
|
|
224
|
+
console.warn(`? [cloudflare] CAPTCHA detected on ${currentUrl} - requires human intervention`);
|
|
225
|
+
if (forceDebug) console.log(`[debug][cloudflare] Skipping automatic bypass due to CAPTCHA requirement`);
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Attempt to solve the challenge with updated methods
|
|
230
|
+
const solveResult = await attemptChallengeSolve(page, currentUrl, challengeInfo, forceDebug);
|
|
231
|
+
result.success = solveResult.success;
|
|
232
|
+
result.error = solveResult.error;
|
|
233
|
+
|
|
234
|
+
} else {
|
|
235
|
+
if (forceDebug) console.log(`[debug][cloudflare] No verification challenge detected on ${currentUrl}`);
|
|
236
|
+
result.success = true; // No challenge to handle
|
|
237
|
+
}
|
|
238
|
+
} catch (error) {
|
|
239
|
+
result.error = error.message;
|
|
240
|
+
if (forceDebug) {
|
|
241
|
+
console.log(`[debug][cloudflare] Challenge check failed for ${currentUrl}:`);
|
|
242
|
+
console.log(`[debug][cloudflare] Error: ${error.message}`);
|
|
243
|
+
console.log(`[debug][cloudflare] Stack: ${error.stack}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Attempts to solve a Cloudflare challenge with modern techniques - Updated for 2025
|
|
252
|
+
* @param {import('puppeteer').Page} page - Puppeteer page instance
|
|
253
|
+
* @param {string} currentUrl - Current URL being processed
|
|
254
|
+
* @param {object} challengeInfo - Challenge analysis results
|
|
255
|
+
* @param {boolean} forceDebug - Debug mode flag
|
|
256
|
+
* @returns {Promise<object>} Solve attempt result
|
|
257
|
+
*/
|
|
258
|
+
async function attemptChallengeSolve(page, currentUrl, challengeInfo, forceDebug = false) {
|
|
259
|
+
const result = {
|
|
260
|
+
success: false,
|
|
261
|
+
error: null,
|
|
262
|
+
method: null
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// Method 1: Handle Turnstile challenges (2025 primary method)
|
|
266
|
+
if (challengeInfo.isTurnstile) {
|
|
267
|
+
try {
|
|
268
|
+
if (forceDebug) console.log(`[debug][cloudflare] Attempting Turnstile method for ${currentUrl}`);
|
|
269
|
+
|
|
270
|
+
const turnstileResult = await handleTurnstileChallenge(page, forceDebug);
|
|
271
|
+
if (turnstileResult.success) {
|
|
272
|
+
result.success = true;
|
|
273
|
+
result.method = 'turnstile';
|
|
274
|
+
if (forceDebug) console.log(`[debug][cloudflare] Turnstile challenge solved successfully for ${currentUrl}`);
|
|
275
|
+
return result;
|
|
276
|
+
} else {
|
|
277
|
+
if (forceDebug) console.log(`[debug][cloudflare] Turnstile method failed: ${turnstileResult.error}`);
|
|
278
|
+
}
|
|
279
|
+
} catch (turnstileError) {
|
|
280
|
+
if (forceDebug) console.log(`[debug][cloudflare] Turnstile method error: ${turnstileError.message}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Method 2: Handle JS challenges (wait for automatic completion)
|
|
285
|
+
if (challengeInfo.isJSChallenge) {
|
|
286
|
+
try {
|
|
287
|
+
if (forceDebug) console.log(`[debug][cloudflare] Attempting JS challenge wait for ${currentUrl}`);
|
|
288
|
+
|
|
289
|
+
const jsResult = await waitForJSChallengeCompletion(page, forceDebug);
|
|
290
|
+
if (jsResult.success) {
|
|
291
|
+
result.success = true;
|
|
292
|
+
result.method = 'js_challenge_wait';
|
|
293
|
+
if (forceDebug) console.log(`[debug][cloudflare] JS challenge completed successfully for ${currentUrl}`);
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
} catch (jsError) {
|
|
297
|
+
if (forceDebug) console.log(`[debug][cloudflare] JS challenge wait error: ${jsError.message}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Method 3: Legacy checkbox interaction (fallback)
|
|
302
|
+
if (challengeInfo.hasLegacyCheckbox) {
|
|
303
|
+
try {
|
|
304
|
+
if (forceDebug) console.log(`[debug][cloudflare] Attempting legacy checkbox method for ${currentUrl}`);
|
|
305
|
+
|
|
306
|
+
const legacyResult = await handleLegacyCheckbox(page, forceDebug);
|
|
307
|
+
if (legacyResult.success) {
|
|
308
|
+
result.success = true;
|
|
309
|
+
result.method = 'legacy_checkbox';
|
|
310
|
+
if (forceDebug) console.log(`[debug][cloudflare] Legacy checkbox method succeeded for ${currentUrl}`);
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
} catch (legacyError) {
|
|
314
|
+
if (forceDebug) console.log(`[debug][cloudflare] Legacy checkbox method error: ${legacyError.message}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Method 4: Alternative element clicking (final fallback)
|
|
319
|
+
try {
|
|
320
|
+
if (forceDebug) console.log(`[debug][cloudflare] Trying alternative click method for ${currentUrl}`);
|
|
321
|
+
|
|
322
|
+
const alternatives = [
|
|
323
|
+
'.cf-challenge-running',
|
|
324
|
+
'[data-ray]',
|
|
325
|
+
'.challenge-stage',
|
|
326
|
+
'.challenge-form'
|
|
327
|
+
];
|
|
328
|
+
|
|
329
|
+
for (const selector of alternatives) {
|
|
330
|
+
try {
|
|
331
|
+
await page.click(selector, { timeout: 3000 });
|
|
332
|
+
await waitForTimeout(page, 3000);
|
|
333
|
+
|
|
334
|
+
// Check if challenge is solved
|
|
335
|
+
const completionCheck = await checkChallengeCompletion(page);
|
|
336
|
+
if (completionCheck.isCompleted) {
|
|
337
|
+
result.success = true;
|
|
338
|
+
result.method = 'alternative_click';
|
|
339
|
+
if (forceDebug) console.log(`[debug][cloudflare] Alternative click method succeeded for ${currentUrl} using ${selector}`);
|
|
340
|
+
return result;
|
|
341
|
+
}
|
|
342
|
+
} catch (clickError) {
|
|
343
|
+
// Continue to next selector
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} catch (altError) {
|
|
348
|
+
result.error = `All bypass methods failed. Last error: ${altError.message}`;
|
|
349
|
+
if (forceDebug) console.log(`[debug][cloudflare] All bypass methods failed for ${currentUrl}: ${altError.message}`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (!result.success) {
|
|
353
|
+
result.error = result.error || 'All challenge bypass methods failed';
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return result;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Handles modern Turnstile challenges - New for 2025
|
|
361
|
+
* @param {import('puppeteer').Page} page - Puppeteer page instance
|
|
362
|
+
* @param {boolean} forceDebug - Debug mode flag
|
|
363
|
+
* @returns {Promise<object>} Turnstile handling result
|
|
364
|
+
*/
|
|
365
|
+
async function handleTurnstileChallenge(page, forceDebug = false) {
|
|
366
|
+
const result = {
|
|
367
|
+
success: false,
|
|
368
|
+
error: null
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
// Wait for Turnstile iframe to load
|
|
373
|
+
const turnstileSelectors = [
|
|
374
|
+
'iframe[src*="challenges.cloudflare.com"]',
|
|
375
|
+
'iframe[title*="Widget containing a Cloudflare"]',
|
|
376
|
+
'iframe[title*="Cloudflare security challenge"]'
|
|
377
|
+
];
|
|
378
|
+
|
|
379
|
+
let turnstileFrame = null;
|
|
380
|
+
for (const selector of turnstileSelectors) {
|
|
381
|
+
try {
|
|
382
|
+
await page.waitForSelector(selector, { timeout: 5000 });
|
|
383
|
+
const frames = await page.frames();
|
|
384
|
+
turnstileFrame = frames.find(frame =>
|
|
385
|
+
frame.url().includes('challenges.cloudflare.com') ||
|
|
386
|
+
frame.url().includes('turnstile')
|
|
387
|
+
);
|
|
388
|
+
if (turnstileFrame) break;
|
|
389
|
+
} catch (e) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (turnstileFrame) {
|
|
395
|
+
if (forceDebug) console.log(`[debug][cloudflare] Found Turnstile iframe`);
|
|
396
|
+
|
|
397
|
+
// Wait for checkbox in iframe
|
|
398
|
+
const checkboxSelectors = [
|
|
399
|
+
'input[type="checkbox"].ctp-checkbox',
|
|
400
|
+
'input[type="checkbox"]',
|
|
401
|
+
'.ctp-checkbox-label',
|
|
402
|
+
'.ctp-checkbox'
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
for (const selector of checkboxSelectors) {
|
|
406
|
+
try {
|
|
407
|
+
await turnstileFrame.waitForSelector(selector, { timeout: 5000 });
|
|
408
|
+
|
|
409
|
+
// Simulate human-like interaction
|
|
410
|
+
await waitForTimeout(page, Math.random() * 1000 + 500);
|
|
411
|
+
await turnstileFrame.click(selector);
|
|
412
|
+
|
|
413
|
+
if (forceDebug) console.log(`[debug][cloudflare] Clicked Turnstile checkbox: ${selector}`);
|
|
414
|
+
break;
|
|
415
|
+
} catch (e) {
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Wait for Turnstile completion
|
|
421
|
+
await page.waitForFunction(
|
|
422
|
+
() => {
|
|
423
|
+
const responseInput = document.querySelector('input[name="cf-turnstile-response"]');
|
|
424
|
+
return responseInput && responseInput.value && responseInput.value.length > 0;
|
|
425
|
+
},
|
|
426
|
+
{ timeout: 30000 }
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
result.success = true;
|
|
430
|
+
} else {
|
|
431
|
+
// Try container-based Turnstile (non-iframe)
|
|
432
|
+
const containerSelectors = [
|
|
433
|
+
'.cf-turnstile',
|
|
434
|
+
'.ctp-checkbox-container',
|
|
435
|
+
'.ctp-checkbox-label'
|
|
436
|
+
];
|
|
437
|
+
|
|
438
|
+
for (const selector of containerSelectors) {
|
|
439
|
+
try {
|
|
440
|
+
await page.waitForSelector(selector, { timeout: 5000 });
|
|
441
|
+
|
|
442
|
+
// Human-like interaction
|
|
443
|
+
await waitForTimeout(page, Math.random() * 1000 + 500);
|
|
444
|
+
await page.click(selector);
|
|
445
|
+
|
|
446
|
+
if (forceDebug) console.log(`[debug][cloudflare] Clicked Turnstile container: ${selector}`);
|
|
447
|
+
|
|
448
|
+
// Wait for completion
|
|
449
|
+
const completionCheck = await checkChallengeCompletion(page);
|
|
450
|
+
if (completionCheck.isCompleted) {
|
|
451
|
+
result.success = true;
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
} catch (e) {
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (!result.success) {
|
|
460
|
+
result.error = 'Turnstile iframe/container not found or not interactive';
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
} catch (error) {
|
|
465
|
+
result.error = `Turnstile handling failed: ${error.message}`;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return result;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Waits for JS challenge completion - New for 2025
|
|
473
|
+
* @param {import('puppeteer').Page} page - Puppeteer page instance
|
|
474
|
+
* @param {boolean} forceDebug - Debug mode flag
|
|
475
|
+
* @returns {Promise<object>} JS challenge result
|
|
476
|
+
*/
|
|
477
|
+
async function waitForJSChallengeCompletion(page, forceDebug = false) {
|
|
478
|
+
const result = {
|
|
479
|
+
success: false,
|
|
480
|
+
error: null
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
try {
|
|
484
|
+
if (forceDebug) console.log(`[debug][cloudflare] Waiting for JS challenge completion`);
|
|
485
|
+
|
|
486
|
+
// Wait for challenge to complete automatically
|
|
487
|
+
await page.waitForFunction(
|
|
488
|
+
() => {
|
|
489
|
+
return !document.body.textContent.includes('Checking your browser') &&
|
|
490
|
+
!document.body.textContent.includes('Please wait while we verify') &&
|
|
491
|
+
!document.querySelector('.cf-challenge-running') &&
|
|
492
|
+
!document.querySelector('[data-cf-challenge]');
|
|
493
|
+
},
|
|
494
|
+
{ timeout: 30000 }
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
result.success = true;
|
|
498
|
+
} catch (error) {
|
|
499
|
+
result.error = `JS challenge timeout: ${error.message}`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return result;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Handles legacy checkbox challenges - Fallback for older implementations
|
|
507
|
+
* @param {import('puppeteer').Page} page - Puppeteer page instance
|
|
508
|
+
* @param {boolean} forceDebug - Debug mode flag
|
|
509
|
+
* @returns {Promise<object>} Legacy challenge result
|
|
510
|
+
*/
|
|
511
|
+
async function handleLegacyCheckbox(page, forceDebug = false) {
|
|
512
|
+
const result = {
|
|
513
|
+
success: false,
|
|
514
|
+
error: null
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
try {
|
|
518
|
+
const legacySelectors = [
|
|
519
|
+
'input[type="checkbox"]#challenge-form',
|
|
520
|
+
'input[type="checkbox"][name="cf_captcha_kind"]',
|
|
521
|
+
'.cf-turnstile input[type="checkbox"]'
|
|
522
|
+
];
|
|
523
|
+
|
|
524
|
+
for (const selector of legacySelectors) {
|
|
525
|
+
try {
|
|
526
|
+
await page.waitForSelector(selector, { timeout: 5000 });
|
|
527
|
+
|
|
528
|
+
const checkbox = await page.$(selector);
|
|
529
|
+
if (checkbox) {
|
|
530
|
+
const box = await checkbox.boundingBox();
|
|
531
|
+
if (box) {
|
|
532
|
+
// Simulate human-like mouse movement
|
|
533
|
+
await page.mouse.move(box.x - 50, box.y - 50);
|
|
534
|
+
await waitForTimeout(page, Math.random() * 500 + 200);
|
|
535
|
+
await page.mouse.move(box.x + box.width/2, box.y + box.height/2, { steps: 5 });
|
|
536
|
+
await waitForTimeout(page, Math.random() * 300 + 100);
|
|
537
|
+
|
|
538
|
+
await checkbox.click();
|
|
539
|
+
if (forceDebug) console.log(`[debug][cloudflare] Clicked legacy checkbox: ${selector}`);
|
|
540
|
+
|
|
541
|
+
// Wait for challenge completion
|
|
542
|
+
const completionCheck = await checkChallengeCompletion(page);
|
|
543
|
+
if (completionCheck.isCompleted) {
|
|
544
|
+
result.success = true;
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
} catch (e) {
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (!result.success) {
|
|
555
|
+
result.error = 'No interactive legacy checkbox found';
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
} catch (error) {
|
|
559
|
+
result.error = `Legacy checkbox handling failed: ${error.message}`;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return result;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Checks if challenge has been completed - New utility function
|
|
567
|
+
* @param {import('puppeteer').Page} page - Puppeteer page instance
|
|
568
|
+
* @returns {Promise<object>} Completion status
|
|
569
|
+
*/
|
|
570
|
+
async function checkChallengeCompletion(page) {
|
|
571
|
+
try {
|
|
572
|
+
const isCompleted = await page.evaluate(() => {
|
|
573
|
+
// Check for absence of challenge indicators
|
|
574
|
+
const noChallengeRunning = !document.querySelector('.cf-challenge-running');
|
|
575
|
+
const noChallengeContainer = !document.querySelector('.cf-challenge-container');
|
|
576
|
+
const noChallengePage = !document.body.textContent.includes('Checking your browser') &&
|
|
577
|
+
!document.body.textContent.includes('Just a moment') &&
|
|
578
|
+
!document.body.textContent.includes('Verify you are human');
|
|
579
|
+
|
|
580
|
+
// Check for completion indicators
|
|
581
|
+
const hasClearanceCookie = document.cookie.includes('cf_clearance');
|
|
582
|
+
const hasTurnstileResponse = document.querySelector('input[name="cf-turnstile-response"]')?.value;
|
|
583
|
+
|
|
584
|
+
return (noChallengeRunning && noChallengeContainer && noChallengePage) ||
|
|
585
|
+
hasClearanceCookie ||
|
|
586
|
+
hasTurnstileResponse;
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
return { isCompleted };
|
|
590
|
+
} catch (error) {
|
|
591
|
+
return { isCompleted: false, error: error.message };
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Main function to handle all Cloudflare challenges for a given page - Updated for 2025
|
|
597
|
+
* @param {import('puppeteer').Page} page - Puppeteer page instance
|
|
598
|
+
* @param {string} currentUrl - Current URL being processed
|
|
599
|
+
* @param {object} siteConfig - Site configuration object
|
|
600
|
+
* @param {boolean} forceDebug - Debug mode flag
|
|
601
|
+
* @returns {Promise<object>} Combined result of all Cloudflare handling
|
|
602
|
+
*/
|
|
603
|
+
async function handleCloudflareProtection(page, currentUrl, siteConfig, forceDebug = false) {
|
|
604
|
+
const result = {
|
|
605
|
+
phishingWarning: { attempted: false, success: false },
|
|
606
|
+
verificationChallenge: { attempted: false, success: false },
|
|
607
|
+
overallSuccess: true,
|
|
608
|
+
errors: []
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
// Handle phishing warnings if enabled
|
|
612
|
+
if (siteConfig.cloudflare_phish === true) {
|
|
613
|
+
const phishingResult = await handlePhishingWarning(page, currentUrl, forceDebug);
|
|
614
|
+
result.phishingWarning = phishingResult;
|
|
615
|
+
|
|
616
|
+
if (phishingResult.attempted && !phishingResult.success) {
|
|
617
|
+
result.overallSuccess = false;
|
|
618
|
+
result.errors.push(`Phishing warning bypass failed: ${phishingResult.error}`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Handle verification challenges if enabled
|
|
623
|
+
if (siteConfig.cloudflare_bypass === true) {
|
|
624
|
+
const challengeResult = await handleVerificationChallenge(page, currentUrl, forceDebug);
|
|
625
|
+
result.verificationChallenge = challengeResult;
|
|
626
|
+
|
|
627
|
+
if (challengeResult.attempted && !challengeResult.success) {
|
|
628
|
+
result.overallSuccess = false;
|
|
629
|
+
if (challengeResult.requiresHuman) {
|
|
630
|
+
result.errors.push(`Human intervention required: ${challengeResult.error}`);
|
|
631
|
+
} else {
|
|
632
|
+
result.errors.push(`Challenge bypass failed: ${challengeResult.error}`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Log overall result
|
|
638
|
+
if (!result.overallSuccess && forceDebug) {
|
|
639
|
+
console.log(`[debug][cloudflare] Overall Cloudflare handling failed for ${currentUrl}:`);
|
|
640
|
+
result.errors.forEach(error => {
|
|
641
|
+
console.log(`[debug][cloudflare] - ${error}`);
|
|
642
|
+
});
|
|
643
|
+
} else if ((result.phishingWarning.attempted || result.verificationChallenge.attempted) && forceDebug) {
|
|
644
|
+
console.log(`[debug][cloudflare] Successfully handled Cloudflare protections for ${currentUrl}`);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return result;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
module.exports = {
|
|
651
|
+
analyzeCloudflareChallenge,
|
|
652
|
+
handlePhishingWarning,
|
|
653
|
+
handleVerificationChallenge,
|
|
654
|
+
handleCloudflareProtection,
|
|
655
|
+
waitForTimeout,
|
|
656
|
+
handleTurnstileChallenge,
|
|
657
|
+
waitForJSChallengeCompletion,
|
|
658
|
+
handleLegacyCheckbox,
|
|
659
|
+
checkChallengeCompletion
|
|
660
|
+
};
|