@fanboynz/network-scanner 2.0.66 → 3.0.0
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 +134 -10
- package/CHANGELOG.md +135 -0
- package/CLAUDE.md +18 -7
- package/README.md +12 -4
- package/lib/adblock-rust.js +23 -18
- package/lib/adblock.js +127 -82
- package/lib/browserexit.js +210 -200
- package/lib/browserhealth.js +84 -60
- package/lib/cdp.js +103 -81
- package/lib/clear_sitedata.js +61 -159
- package/lib/cloudflare.js +579 -409
- package/lib/colorize.js +29 -12
- package/lib/compare.js +16 -8
- package/lib/compress.js +2 -1
- package/lib/curl.js +287 -220
- package/lib/domain-cache.js +87 -40
- package/lib/dry-run.js +137 -194
- package/lib/fingerprint.js +20 -18
- package/lib/flowproxy.js +391 -188
- package/lib/ghost-cursor.js +8 -7
- package/lib/grep.js +248 -171
- package/lib/ignore_similar.js +70 -124
- package/lib/interaction.js +132 -235
- package/lib/nettools.js +309 -87
- package/lib/openvpn_vpn.js +12 -11
- package/lib/output.js +92 -59
- package/lib/post-processing.js +216 -162
- package/lib/redirect.js +46 -30
- package/lib/referrer.js +158 -165
- package/lib/searchstring.js +290 -381
- package/lib/smart-cache.js +141 -91
- package/lib/socks-relay.js +8 -7
- package/lib/spawn-async.js +137 -0
- package/lib/validate_rules.js +188 -176
- package/lib/wireguard_vpn.js +111 -117
- package/nwss.js +740 -156
- package/package.json +4 -4
package/lib/interaction.js
CHANGED
|
@@ -34,8 +34,7 @@
|
|
|
34
34
|
* - mouseMovements: number - Number of mouse movements to perform
|
|
35
35
|
* - includeScrolling: boolean - Enable scrolling simulation
|
|
36
36
|
* - includeElementClicks: boolean - Enable safe element clicking
|
|
37
|
-
*
|
|
38
|
-
*
|
|
37
|
+
*
|
|
39
38
|
* ANTI-DETECTION FEATURES:
|
|
40
39
|
* - Variable timing between actions
|
|
41
40
|
* - Curved mouse movements (not straight lines)
|
|
@@ -54,6 +53,14 @@
|
|
|
54
53
|
* @requires puppeteer
|
|
55
54
|
*/
|
|
56
55
|
|
|
56
|
+
const { formatLogMessage, messageColors } = require('./colorize');
|
|
57
|
+
|
|
58
|
+
// Precomputed colored '[interaction]' subsystem prefix. formatLogMessage only
|
|
59
|
+
// colors the [severity] tag; this constant colors the subsystem prefix so
|
|
60
|
+
// '[debug] [interaction] X' has both tags visually distinct, matching how
|
|
61
|
+
// every other module in the codebase emits its subsystem logs.
|
|
62
|
+
const INTERACTION_TAG = messageColors.processing('[interaction]');
|
|
63
|
+
|
|
57
64
|
// Fast setTimeout helper for Puppeteer 22.x compatibility
|
|
58
65
|
// Uses standard Promise constructor for better performance than node:timers/promises
|
|
59
66
|
function fastTimeout(ms) {
|
|
@@ -75,16 +82,21 @@ const COORDINATE_MARGINS = {
|
|
|
75
82
|
};
|
|
76
83
|
|
|
77
84
|
// === MOUSE MOVEMENT CONSTANTS ===
|
|
78
|
-
//
|
|
85
|
+
// Values reflect what the code ACTUALLY uses after the perf scale-back —
|
|
86
|
+
// the original 5-30 step plan was cut to 2-8 to keep per-URL interaction
|
|
87
|
+
// under ~300ms. Curve/jitter math is mostly cosmetic at these step counts;
|
|
88
|
+
// the scanner doesn't need elaborate anti-bot curves to trigger onclick/
|
|
89
|
+
// scroll handlers, so this is the right size.
|
|
79
90
|
const MOUSE_MOVEMENT = {
|
|
80
|
-
MIN_STEPS:
|
|
81
|
-
DEFAULT_STEPS:
|
|
82
|
-
MAX_STEPS:
|
|
91
|
+
MIN_STEPS: 2, // Minimum steps for any movement
|
|
92
|
+
DEFAULT_STEPS: 6, // Default steps when distance-derived
|
|
93
|
+
MAX_STEPS: 8, // Hard cap to keep movements under ~300ms
|
|
83
94
|
MIN_DELAY: 5, // Minimum milliseconds between movement steps
|
|
84
95
|
MAX_DELAY: 25, // Maximum milliseconds between movement steps
|
|
85
|
-
|
|
96
|
+
MAX_TOTAL_MS: 300, // Emergency cap on total movement time
|
|
97
|
+
DEFAULT_CURVE: 0.2, // Default curve intensity (mostly cosmetic at low step counts)
|
|
86
98
|
DEFAULT_JITTER: 2, // Default random jitter in pixels
|
|
87
|
-
DISTANCE_STEP_RATIO: 200, //
|
|
99
|
+
DISTANCE_STEP_RATIO: 200, // Pixels per step when auto-calculating step count
|
|
88
100
|
CURVE_INTENSITY_RATIO: 0.01 // Multiplier for curve calculation
|
|
89
101
|
};
|
|
90
102
|
|
|
@@ -105,13 +117,9 @@ const TIMING = {
|
|
|
105
117
|
CLICK_PAUSE_MAX: 200, // Maximum pause before clicking
|
|
106
118
|
POST_CLICK_MIN: 300, // Minimum pause after clicking
|
|
107
119
|
POST_CLICK_MAX: 500, // Maximum pause after clicking
|
|
108
|
-
TYPING_MIN_DELAY: 50, // Minimum delay between keystrokes
|
|
109
|
-
TYPING_MAX_DELAY: 150, // Maximum delay between keystrokes
|
|
110
|
-
MISTAKE_PAUSE_MIN: 100, // Minimum pause after typing mistake
|
|
111
|
-
MISTAKE_PAUSE_MAX: 200, // Maximum pause after typing mistake
|
|
112
|
-
BACKSPACE_DELAY_MIN: 50, // Minimum delay before backspace
|
|
113
|
-
BACKSPACE_DELAY_MAX: 100, // Maximum delay before backspace
|
|
114
120
|
DEFAULT_INTERACTION_DURATION: 2000 // Default total interaction time
|
|
121
|
+
// Note: TYPING_*, MISTAKE_PAUSE_*, BACKSPACE_DELAY_* removed along with
|
|
122
|
+
// simulateTyping() — they were only consumed by that dead-code function.
|
|
115
123
|
};
|
|
116
124
|
|
|
117
125
|
// === ELEMENT INTERACTION CONSTANTS ===
|
|
@@ -119,8 +127,9 @@ const TIMING = {
|
|
|
119
127
|
const ELEMENT_INTERACTION = {
|
|
120
128
|
MAX_ATTEMPTS: 3, // Maximum attempts to find clickable elements
|
|
121
129
|
TIMEOUT: 2000, // Timeout for element operations
|
|
122
|
-
TEXT_PREVIEW_LENGTH: 50
|
|
123
|
-
|
|
130
|
+
TEXT_PREVIEW_LENGTH: 50 // Characters to capture for element text preview
|
|
131
|
+
// Note: MISTAKE_RATE removed with simulateTyping() — it was that function's
|
|
132
|
+
// only consumer.
|
|
124
133
|
};
|
|
125
134
|
|
|
126
135
|
// === CONTENT CLICK CONSTANTS ===
|
|
@@ -132,7 +141,11 @@ const CONTENT_CLICK = {
|
|
|
132
141
|
CLICK_COUNT: 2, // Two attempts (primary + backup if first suppressed)
|
|
133
142
|
INTER_CLICK_MIN: 300, // Minimum ms between clicks (above Monetag 250ms cooldown)
|
|
134
143
|
INTER_CLICK_MAX: 500, // Maximum ms between clicks
|
|
135
|
-
PRE_CLICK_DELAY:
|
|
144
|
+
// PRE_CLICK_DELAY: most ad scripts register document-level listeners
|
|
145
|
+
// within 50–100ms of DOMContentLoaded. The prior mouse/scroll activity
|
|
146
|
+
// (~500ms+) gives them plenty of head start before this fires, so the
|
|
147
|
+
// 300ms buffer here was mostly defensive. Reduced to 100ms.
|
|
148
|
+
PRE_CLICK_DELAY: 100,
|
|
136
149
|
VIEWPORT_INSET: 0.2, // Avoid outer 20% of viewport (menus, overlays)
|
|
137
150
|
MOUSE_APPROACH_STEPS: 3 // Minimal steps — just enough for non-instant movement
|
|
138
151
|
};
|
|
@@ -168,7 +181,13 @@ const SITE_DURATIONS = {
|
|
|
168
181
|
// === PROBABILITY CONSTANTS ===
|
|
169
182
|
// Control randomness and behavior patterns
|
|
170
183
|
const PROBABILITIES = {
|
|
171
|
-
PAUSE_CHANCE:
|
|
184
|
+
// PAUSE_CHANCE: probability of a 25–75ms idle pause between mouse motions.
|
|
185
|
+
// Bot detectors mainly look at timing variance WITHIN a single mouse trail
|
|
186
|
+
// (the per-step delays in humanLikeMouseMove already cover that), so the
|
|
187
|
+
// inter-movement pause is mostly cosmetic. Lowered from 0.3 to 0.1 to cut
|
|
188
|
+
// ~30ms average per interaction while keeping enough variance to avoid
|
|
189
|
+
// perfectly metronomic action spacing.
|
|
190
|
+
PAUSE_CHANCE: 0.1,
|
|
172
191
|
SCROLL_DOWN_BIAS: 0.7, // 70% chance to scroll down (vs up)
|
|
173
192
|
EDGE_PREFERENCE: { // Probabilities for edge selection in preferEdges mode
|
|
174
193
|
LEFT: 0.25, // 0-25% = left edge
|
|
@@ -178,34 +197,19 @@ const PROBABILITIES = {
|
|
|
178
197
|
}
|
|
179
198
|
};
|
|
180
199
|
|
|
181
|
-
// === PERFORMANCE OPTIMIZATION VARIABLES ===
|
|
182
|
-
// Viewport caching to reduce repeated page.viewport() calls
|
|
183
|
-
let cachedViewport = null;
|
|
184
|
-
let lastViewportCheck = 0;
|
|
185
|
-
const VIEWPORT_CACHE_DURATION = 30000; // 30 seconds
|
|
186
|
-
let interactionMemoryCleanupCounter = 0;
|
|
187
|
-
|
|
188
200
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
201
|
+
* Returns viewport dimensions, with a safe fallback to DEFAULT_VIEWPORT if
|
|
202
|
+
* the page hasn't been given a viewport or the query throws. No caching —
|
|
203
|
+
* page.viewport() is an in-memory getter, not a CDP round-trip, so there's
|
|
204
|
+
* nothing to save by caching it.
|
|
205
|
+
*
|
|
192
206
|
* @param {import('puppeteer').Page} page - Puppeteer page object
|
|
193
207
|
* @returns {Promise<object>} Viewport dimensions {width, height}
|
|
194
208
|
*/
|
|
195
|
-
async function
|
|
196
|
-
const now = Date.now();
|
|
197
|
-
|
|
198
|
-
// Return cached viewport if still valid
|
|
199
|
-
if (cachedViewport && (now - lastViewportCheck) < VIEWPORT_CACHE_DURATION) {
|
|
200
|
-
return cachedViewport;
|
|
201
|
-
}
|
|
202
|
-
|
|
209
|
+
async function getViewport(page) {
|
|
203
210
|
try {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
return cachedViewport || { width: DEFAULT_VIEWPORT.WIDTH, height: DEFAULT_VIEWPORT.HEIGHT };
|
|
207
|
-
} catch (viewportErr) {
|
|
208
|
-
// Return defaults if viewport query fails
|
|
211
|
+
return (await page.viewport()) || { width: DEFAULT_VIEWPORT.WIDTH, height: DEFAULT_VIEWPORT.HEIGHT };
|
|
212
|
+
} catch (_) {
|
|
209
213
|
return { width: DEFAULT_VIEWPORT.WIDTH, height: DEFAULT_VIEWPORT.HEIGHT };
|
|
210
214
|
}
|
|
211
215
|
}
|
|
@@ -278,11 +282,20 @@ function generateRandomCoordinates(maxX = DEFAULT_VIEWPORT.WIDTH, maxY = DEFAULT
|
|
|
278
282
|
const centerX = maxX / 2;
|
|
279
283
|
const centerY = maxY / 2;
|
|
280
284
|
const avoidRadius = Math.min(maxX, maxY) * COORDINATE_MARGINS.CENTER_AVOID_RATIO;
|
|
281
|
-
|
|
285
|
+
|
|
286
|
+
// Iteration cap: for sensible viewports a valid point is found in ~1
|
|
287
|
+
// try, but a pathologically small viewport could in principle have
|
|
288
|
+
// every candidate inside avoidRadius. After 50 attempts give up and
|
|
289
|
+
// return the last sample — a near-center point is preferable to an
|
|
290
|
+
// infinite loop.
|
|
291
|
+
let attempts = 0;
|
|
282
292
|
do {
|
|
283
293
|
x = Math.floor(Math.random() * (maxX - 2 * marginX)) + marginX;
|
|
284
294
|
y = Math.floor(Math.random() * (maxY - 2 * marginY)) + marginY;
|
|
285
|
-
} while (
|
|
295
|
+
} while (
|
|
296
|
+
Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2) < avoidRadius &&
|
|
297
|
+
++attempts < 50
|
|
298
|
+
);
|
|
286
299
|
} else {
|
|
287
300
|
// Standard random coordinates
|
|
288
301
|
x = Math.floor(Math.random() * (maxX - 2 * marginX)) + marginX;
|
|
@@ -349,28 +362,25 @@ async function humanLikeMouseMove(page, fromX, fromY, toX, toY, options = {}) {
|
|
|
349
362
|
} = options;
|
|
350
363
|
|
|
351
364
|
const distance = Math.sqrt((toX - fromX) ** 2 + (toY - fromY) ** 2);
|
|
352
|
-
|
|
353
|
-
//
|
|
365
|
+
|
|
366
|
+
// Step count: caller-provided value capped at MAX_STEPS, otherwise derived
|
|
367
|
+
// from distance and clamped to [MIN_STEPS, DEFAULT_STEPS].
|
|
354
368
|
let actualSteps;
|
|
355
369
|
if (options.steps) {
|
|
356
|
-
|
|
357
|
-
actualSteps = Math.min(options.steps, 8); // Was MAX_STEPS (30), now max 8
|
|
370
|
+
actualSteps = Math.min(options.steps, MOUSE_MOVEMENT.MAX_STEPS);
|
|
358
371
|
} else {
|
|
359
|
-
// Calculate steps based on distance with strict limits
|
|
360
372
|
const calculatedSteps = Math.floor(distance / MOUSE_MOVEMENT.DISTANCE_STEP_RATIO);
|
|
361
373
|
actualSteps = Math.max(
|
|
362
|
-
|
|
363
|
-
Math.min(calculatedSteps,
|
|
374
|
+
MOUSE_MOVEMENT.MIN_STEPS,
|
|
375
|
+
Math.min(calculatedSteps, MOUSE_MOVEMENT.DEFAULT_STEPS)
|
|
364
376
|
);
|
|
365
377
|
}
|
|
366
378
|
|
|
367
|
-
//
|
|
368
|
-
|
|
379
|
+
// Emergency cap on total movement time — if step count × max-per-step delay
|
|
380
|
+
// would exceed the budget, reduce step count to fit.
|
|
369
381
|
const estimatedTime = actualSteps * maxDelay;
|
|
370
|
-
if (estimatedTime >
|
|
371
|
-
actualSteps = Math.floor(
|
|
372
|
-
// Optional: Log performance cap without forceDebug dependency
|
|
373
|
-
// console.log(`[interaction] Capped steps to ${actualSteps} to prevent timeout (estimated: ${estimatedTime}ms)`);
|
|
382
|
+
if (estimatedTime > MOUSE_MOVEMENT.MAX_TOTAL_MS) {
|
|
383
|
+
actualSteps = Math.floor(MOUSE_MOVEMENT.MAX_TOTAL_MS / maxDelay);
|
|
374
384
|
}
|
|
375
385
|
|
|
376
386
|
for (let i = 0; i <= actualSteps; i++) {
|
|
@@ -514,6 +524,7 @@ async function simulateScrolling(page, options = {}) {
|
|
|
514
524
|
* @param {string[]} options.elementTypes - CSS selectors for clickable elements
|
|
515
525
|
* @param {boolean} options.avoidDestructive - Avoid dangerous actions
|
|
516
526
|
* @param {number} options.timeout - Timeout for element operations
|
|
527
|
+
* @param {boolean} options.forceDebug - Enable debug logging for skipped paths
|
|
517
528
|
*
|
|
518
529
|
* @example
|
|
519
530
|
* // Safe element interaction (default)
|
|
@@ -545,24 +556,28 @@ async function interactWithElements(page, options = {}) {
|
|
|
545
556
|
// Check if page is closed before attempting interaction
|
|
546
557
|
if (page.isClosed()) {
|
|
547
558
|
if (options.forceDebug) {
|
|
548
|
-
console.log(
|
|
559
|
+
console.log(formatLogMessage('debug', `${INTERACTION_TAG} Page is closed, skipping element interaction`));
|
|
549
560
|
}
|
|
550
561
|
return;
|
|
551
562
|
}
|
|
552
563
|
|
|
553
|
-
// Very short timeout since page should already be loaded
|
|
554
|
-
|
|
564
|
+
// Very short timeout since page should already be loaded.
|
|
565
|
+
// Explicitly dispose the returned handle rather than relying on
|
|
566
|
+
// Puppeteer's FinalizationRegistry — matches the dispose pattern
|
|
567
|
+
// already used in performPageInteraction's final-hover block.
|
|
568
|
+
const bodyHandle = await page.waitForSelector('body', { timeout: 1000 });
|
|
569
|
+
if (bodyHandle) { try { await bodyHandle.dispose(); } catch (_) {} }
|
|
555
570
|
// Re-check after async wait — page may have closed during selector wait
|
|
556
571
|
if (page.isClosed()) return;
|
|
557
572
|
} catch (bodyWaitErr) {
|
|
558
573
|
if (options.forceDebug) {
|
|
559
|
-
console.log(
|
|
574
|
+
console.log(formatLogMessage('debug', `${INTERACTION_TAG} Page not ready for element interaction: ${bodyWaitErr.message}`));
|
|
560
575
|
}
|
|
561
576
|
return;
|
|
562
577
|
}
|
|
563
578
|
|
|
564
579
|
// Use cached viewport for better performance
|
|
565
|
-
const viewport = await
|
|
580
|
+
const viewport = await getViewport(page);
|
|
566
581
|
const maxX = viewport.width;
|
|
567
582
|
const maxY = viewport.height;
|
|
568
583
|
|
|
@@ -670,7 +685,7 @@ async function performContentClicks(page, options = {}) {
|
|
|
670
685
|
try {
|
|
671
686
|
if (page.isClosed()) return;
|
|
672
687
|
|
|
673
|
-
const viewport = await
|
|
688
|
+
const viewport = await getViewport(page);
|
|
674
689
|
const inset = CONTENT_CLICK.VIEWPORT_INSET;
|
|
675
690
|
const minX = Math.floor(viewport.width * inset);
|
|
676
691
|
const maxX = Math.floor(viewport.width * (1 - inset));
|
|
@@ -702,7 +717,7 @@ async function performContentClicks(page, options = {}) {
|
|
|
702
717
|
await page.mouse.click(targetX, targetY);
|
|
703
718
|
|
|
704
719
|
if (forceDebug) {
|
|
705
|
-
console.log(
|
|
720
|
+
console.log(formatLogMessage('debug', `${INTERACTION_TAG} Content click ${i + 1}/${clicks} at (${targetX}, ${targetY})`));
|
|
706
721
|
}
|
|
707
722
|
|
|
708
723
|
lastX = targetX;
|
|
@@ -718,110 +733,6 @@ async function performContentClicks(page, options = {}) {
|
|
|
718
733
|
}
|
|
719
734
|
}
|
|
720
735
|
|
|
721
|
-
/**
|
|
722
|
-
* Simulates realistic typing behavior with human characteristics
|
|
723
|
-
*
|
|
724
|
-
* TYPING CHARACTERISTICS:
|
|
725
|
-
* - Variable delay between keystrokes
|
|
726
|
-
* - Optional typing mistakes with correction
|
|
727
|
-
* - Realistic backspace timing
|
|
728
|
-
* - Character-by-character typing (not paste)
|
|
729
|
-
*
|
|
730
|
-
* MISTAKE SIMULATION:
|
|
731
|
-
* - Random wrong characters (2% default rate)
|
|
732
|
-
* - Pause after mistake (human realization delay)
|
|
733
|
-
* - Backspace to correct
|
|
734
|
-
* - Continue with correct character
|
|
735
|
-
*
|
|
736
|
-
* DEVELOPER NOTES:
|
|
737
|
-
* - Requires an active input field with focus
|
|
738
|
-
* - All typing errors are silently handled
|
|
739
|
-
* - Mistake rate should be low (0.01-0.05) for realism
|
|
740
|
-
* - Use for form filling or search simulation
|
|
741
|
-
*
|
|
742
|
-
* @param {import('puppeteer').Page} page - Puppeteer page object
|
|
743
|
-
* @param {string} text - Text to type
|
|
744
|
-
* @param {object} options - Typing configuration
|
|
745
|
-
* @param {number} options.minDelay - Minimum delay between keystrokes
|
|
746
|
-
* @param {number} options.maxDelay - Maximum delay between keystrokes
|
|
747
|
-
* @param {boolean} options.mistakes - Enable typing mistakes
|
|
748
|
-
* @param {number} options.mistakeRate - Probability of mistakes (0.0-1.0)
|
|
749
|
-
*
|
|
750
|
-
* @example
|
|
751
|
-
* // Basic typing
|
|
752
|
-
* await simulateTyping(page, "hello world");
|
|
753
|
-
*
|
|
754
|
-
* // Slow typing with mistakes
|
|
755
|
-
* await simulateTyping(page, "search query", {
|
|
756
|
-
* minDelay: 100, maxDelay: 300, mistakes: true, mistakeRate: 0.03
|
|
757
|
-
* });
|
|
758
|
-
*
|
|
759
|
-
* // Fast typing without mistakes
|
|
760
|
-
* await simulateTyping(page, "username", {
|
|
761
|
-
* minDelay: 30, maxDelay: 80, mistakes: false
|
|
762
|
-
* });
|
|
763
|
-
*/
|
|
764
|
-
async function simulateTyping(page, text, options = {}) {
|
|
765
|
-
const {
|
|
766
|
-
minDelay = TIMING.TYPING_MIN_DELAY,
|
|
767
|
-
maxDelay = TIMING.TYPING_MAX_DELAY,
|
|
768
|
-
mistakes = false,
|
|
769
|
-
mistakeRate = ELEMENT_INTERACTION.MISTAKE_RATE
|
|
770
|
-
} = options;
|
|
771
|
-
|
|
772
|
-
try {
|
|
773
|
-
// Ensure page is ready for typing
|
|
774
|
-
try {
|
|
775
|
-
await page.waitForSelector('body', { timeout: 1000 });
|
|
776
|
-
} catch (bodyWaitErr) {
|
|
777
|
-
return; // Silently skip typing if page not ready
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
for (let i = 0; i < text.length; i++) {
|
|
781
|
-
const char = text[i];
|
|
782
|
-
|
|
783
|
-
// Simulate occasional typing mistakes
|
|
784
|
-
if (mistakes && Math.random() < mistakeRate) {
|
|
785
|
-
const wrongChar = String.fromCharCode(97 + Math.floor(Math.random() * 26));
|
|
786
|
-
await page.keyboard.type(wrongChar);
|
|
787
|
-
await fastTimeout(TIMING.MISTAKE_PAUSE_MIN + Math.random() * (TIMING.MISTAKE_PAUSE_MAX - TIMING.MISTAKE_PAUSE_MIN));
|
|
788
|
-
await page.keyboard.press('Backspace');
|
|
789
|
-
await fastTimeout(TIMING.BACKSPACE_DELAY_MIN + Math.random() * (TIMING.BACKSPACE_DELAY_MAX - TIMING.BACKSPACE_DELAY_MIN));
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
await page.keyboard.type(char);
|
|
793
|
-
|
|
794
|
-
// Variable delay between keystrokes
|
|
795
|
-
const delay = Math.floor(Math.random() * (maxDelay - minDelay + 1)) + minDelay;
|
|
796
|
-
await fastTimeout(delay);
|
|
797
|
-
}
|
|
798
|
-
} catch (typingErr) {
|
|
799
|
-
// Silently handle typing errors
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
/**
|
|
804
|
-
* Cleans up interaction-related memory and cached data
|
|
805
|
-
* Should be called periodically in long-running sessions
|
|
806
|
-
*
|
|
807
|
-
* @param {boolean} force - Force cleanup regardless of timing
|
|
808
|
-
*/
|
|
809
|
-
function cleanupInteractionMemory(force = false) {
|
|
810
|
-
interactionMemoryCleanupCounter++;
|
|
811
|
-
|
|
812
|
-
// Only cleanup every 10 calls unless forced
|
|
813
|
-
if (!force && interactionMemoryCleanupCounter % 10 !== 0) {
|
|
814
|
-
return;
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
// Clear cached viewport if it's older than cache duration
|
|
818
|
-
const now = Date.now();
|
|
819
|
-
if (cachedViewport && (now - lastViewportCheck) > VIEWPORT_CACHE_DURATION) {
|
|
820
|
-
cachedViewport = null;
|
|
821
|
-
lastViewportCheck = 0;
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
|
|
825
736
|
/**
|
|
826
737
|
* Performs comprehensive page interaction simulation - MAIN ENTRY POINT
|
|
827
738
|
*
|
|
@@ -858,7 +769,6 @@ function cleanupInteractionMemory(force = false) {
|
|
|
858
769
|
* @param {number} options.mouseMovements - Number of mouse movements
|
|
859
770
|
* @param {boolean} options.includeScrolling - Enable scrolling simulation
|
|
860
771
|
* @param {boolean} options.includeElementClicks - Enable element clicking
|
|
861
|
-
* @param {boolean} options.includeTyping - Enable typing simulation
|
|
862
772
|
* @param {number} options.duration - Total interaction time in milliseconds
|
|
863
773
|
* @param {string} options.intensity - 'low' | 'medium' | 'high'
|
|
864
774
|
* @param {boolean} forceDebug - Enable debug logging
|
|
@@ -883,11 +793,15 @@ function cleanupInteractionMemory(force = false) {
|
|
|
883
793
|
* });
|
|
884
794
|
*/
|
|
885
795
|
async function performPageInteraction(page, currentUrl, options = {}, forceDebug = false) {
|
|
796
|
+
// mouseMovements deliberately has no default in the destructure: we want
|
|
797
|
+
// to distinguish 'caller didn't pass it' from 'caller explicitly passed 3'
|
|
798
|
+
// so the actualMovements calculation below can let intensity drive the
|
|
799
|
+
// count when nothing was specified. The old default-3 + Math.min(...) shape
|
|
800
|
+
// silently capped HIGH intensity's intended 5 movements down to 3.
|
|
886
801
|
const {
|
|
887
|
-
mouseMovements
|
|
802
|
+
mouseMovements,
|
|
888
803
|
includeScrolling = true,
|
|
889
804
|
includeElementClicks = false,
|
|
890
|
-
includeTyping = false,
|
|
891
805
|
duration = TIMING.DEFAULT_INTERACTION_DURATION,
|
|
892
806
|
intensity = 'medium'
|
|
893
807
|
} = options;
|
|
@@ -905,26 +819,29 @@ async function performPageInteraction(page, currentUrl, options = {}, forceDebug
|
|
|
905
819
|
try {
|
|
906
820
|
if (page.isClosed()) {
|
|
907
821
|
if (forceDebug) {
|
|
908
|
-
console.log(
|
|
822
|
+
console.log(formatLogMessage('debug', `${INTERACTION_TAG} Page is closed for ${currentUrl}, skipping interaction`));
|
|
909
823
|
}
|
|
910
824
|
return;
|
|
911
825
|
}
|
|
912
826
|
} catch { return; }
|
|
913
827
|
|
|
914
828
|
// Use cached viewport for better performance
|
|
915
|
-
const viewport = await
|
|
829
|
+
const viewport = await getViewport(page);
|
|
916
830
|
const maxX = viewport.width;
|
|
917
831
|
const maxY = viewport.height;
|
|
918
832
|
|
|
919
833
|
if (forceDebug) {
|
|
920
834
|
let hostname = currentUrl;
|
|
921
835
|
try { hostname = new URL(currentUrl).hostname; } catch {}
|
|
922
|
-
console.log(
|
|
836
|
+
console.log(formatLogMessage('debug', `${INTERACTION_TAG} Starting enhanced interaction simulation for ${hostname} (${intensity} intensity)`));
|
|
923
837
|
}
|
|
924
838
|
|
|
925
|
-
// Configure intensity settings
|
|
839
|
+
// Configure intensity settings. When the caller didn't pass mouseMovements,
|
|
840
|
+
// intensity drives the count (HIGH = 5, MEDIUM = 3, LOW = 2). When the
|
|
841
|
+
// caller DID pass an explicit value, that wins — covers callers who want
|
|
842
|
+
// a custom count regardless of the broader intensity profile.
|
|
926
843
|
const settings = INTENSITY_SETTINGS[intensity.toUpperCase()] || INTENSITY_SETTINGS.MEDIUM;
|
|
927
|
-
const actualMovements =
|
|
844
|
+
const actualMovements = mouseMovements !== undefined ? mouseMovements : settings.movements;
|
|
928
845
|
|
|
929
846
|
// Start with random position
|
|
930
847
|
let currentPos = generateRandomCoordinates(maxX, maxY, { preferEdges: true });
|
|
@@ -937,12 +854,14 @@ async function performPageInteraction(page, currentUrl, options = {}, forceDebug
|
|
|
937
854
|
}
|
|
938
855
|
|
|
939
856
|
|
|
940
|
-
|
|
941
|
-
//
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
//
|
|
857
|
+
// Inter-action spacers were a separate await-fastTimeout between every
|
|
858
|
+
// movement/scroll (capped at 50ms each). Each movement already has its
|
|
859
|
+
// own internal step delays (5–25ms per step) plus the PAUSE_CHANCE
|
|
860
|
+
// pause path, so the cursor never visibly "rips through" actions —
|
|
861
|
+
// the spacer was adding ~50ms × 5 actions = ~250ms of pure dead time
|
|
862
|
+
// per interaction for no anti-detection benefit. Removed.
|
|
863
|
+
|
|
864
|
+
// Start timing ONLY the actual interaction operations
|
|
946
865
|
const actualInteractionStartTime = Date.now();
|
|
947
866
|
|
|
948
867
|
// Perform mouse movements
|
|
@@ -961,13 +880,13 @@ async function performPageInteraction(page, currentUrl, options = {}, forceDebug
|
|
|
961
880
|
|
|
962
881
|
currentPos = targetPos;
|
|
963
882
|
|
|
964
|
-
// Occasional pause
|
|
883
|
+
// Occasional pause — keep at lower probability than the prior 0.3
|
|
884
|
+
// (PROBABILITIES.PAUSE_CHANCE) since bot detectors care more about
|
|
885
|
+
// timing variance WITHIN mouse motion (the step delays handle that)
|
|
886
|
+
// than about pauses BETWEEN motions.
|
|
965
887
|
if (Math.random() < PROBABILITIES.PAUSE_CHANCE) {
|
|
966
|
-
await fastTimeout(25 + Math.random() * 50);
|
|
888
|
+
await fastTimeout(25 + Math.random() * 50);
|
|
967
889
|
}
|
|
968
|
-
|
|
969
|
-
// Time-based spacing
|
|
970
|
-
await fastTimeout(Math.min(actionInterval, 50)); // CRITICAL: Cap at 50ms
|
|
971
890
|
}
|
|
972
891
|
|
|
973
892
|
// Scrolling simulation
|
|
@@ -980,65 +899,46 @@ async function performPageInteraction(page, currentUrl, options = {}, forceDebug
|
|
|
980
899
|
amount: 1 + Math.floor(Math.random() * 2), // CRITICAL: Less scrolling
|
|
981
900
|
smoothness: 1 + Math.floor(Math.random() * 2) // CRITICAL: Much less smooth
|
|
982
901
|
});
|
|
983
|
-
|
|
984
|
-
await fastTimeout(Math.min(actionInterval, 100)); // CRITICAL: Cap intervals
|
|
985
902
|
}
|
|
986
903
|
}
|
|
987
904
|
|
|
988
|
-
// Click interaction —
|
|
989
|
-
//
|
|
990
|
-
//
|
|
991
|
-
//
|
|
992
|
-
//
|
|
905
|
+
// Click interaction — content-area clicks trigger document-level onclick
|
|
906
|
+
// handlers (Monetag, similar popunder SDKs). Previously this branch ALSO
|
|
907
|
+
// called interactWithElements afterward to hit element-specific listeners,
|
|
908
|
+
// but document-level handlers cover ~all real-world ad-script patterns —
|
|
909
|
+
// the secondary element-click pass added ~200–600ms for marginal coverage.
|
|
910
|
+
// interactWithElements is still exported for callers that want it.
|
|
993
911
|
if (includeElementClicks) {
|
|
994
912
|
if (checkTimeout()) return; // Emergency timeout check
|
|
995
|
-
// Primary: content area clicks for document-level onclick handlers
|
|
996
913
|
await performContentClicks(page, { forceDebug });
|
|
997
|
-
// Secondary: targeted element clicks (fast, 1 attempt only)
|
|
998
|
-
if (!checkTimeout()) {
|
|
999
|
-
await interactWithElements(page, {
|
|
1000
|
-
maxAttempts: 1,
|
|
1001
|
-
avoidDestructive: true
|
|
1002
|
-
});
|
|
1003
|
-
}
|
|
1004
914
|
}
|
|
1005
915
|
|
|
1006
|
-
//
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
//
|
|
916
|
+
// Final resting position — single mouse.move instead of the previous
|
|
917
|
+
// humanLikeMouseMove + page.hover('body') sequence. The prior block paid
|
|
918
|
+
// ~80–120ms (full anti-detection trajectory + a CDP $() lookup + hover
|
|
919
|
+
// round-trip) just to leave the cursor at a random end position. The
|
|
920
|
+
// anti-detection curves matter while actions are happening; the parking
|
|
921
|
+
// move at the end doesn't need them, and hover('body') was mostly a no-op
|
|
922
|
+
// since the cursor was already inside the body's bounding box anyway.
|
|
1010
923
|
const finalPos = generateRandomCoordinates(maxX, maxY);
|
|
1011
|
-
await
|
|
1012
|
-
|
|
1013
|
-
// Safe hover with validation
|
|
1014
|
-
try {
|
|
1015
|
-
const bodyElement = await page.$('body');
|
|
1016
|
-
if (bodyElement) {
|
|
1017
|
-
try {
|
|
1018
|
-
await page.hover('body');
|
|
1019
|
-
} finally {
|
|
1020
|
-
await bodyElement.dispose();
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
} catch (hoverErr) {
|
|
1024
|
-
// Silently handle hover failures - not critical
|
|
1025
|
-
}
|
|
924
|
+
try { await page.mouse.move(finalPos.x, finalPos.y); }
|
|
925
|
+
catch (_) { /* page closed or detached — non-critical */ }
|
|
1026
926
|
|
|
1027
927
|
// End timing ONLY after actual interaction operations complete
|
|
1028
928
|
const interactionElapsedTime = Date.now() - actualInteractionStartTime
|
|
1029
929
|
|
|
1030
930
|
// CRITICAL: Warn about slow interactions
|
|
1031
931
|
if (interactionElapsedTime > 8000) {
|
|
1032
|
-
console.warn(
|
|
932
|
+
console.warn(formatLogMessage('warn', `${INTERACTION_TAG} WARNING: Interaction took ${interactionElapsedTime}ms for ${currentUrl}`));
|
|
1033
933
|
}
|
|
1034
934
|
|
|
1035
935
|
if (forceDebug) {
|
|
1036
|
-
console.log(
|
|
936
|
+
console.log(formatLogMessage('debug', `${INTERACTION_TAG} Completed interaction simulation in ${interactionElapsedTime}ms (${actualMovements} movements, ${includeScrolling ? settings.scrolls : 0} scrolls)`));
|
|
1037
937
|
}
|
|
1038
938
|
|
|
1039
939
|
} catch (interactionErr) {
|
|
1040
940
|
if (forceDebug) {
|
|
1041
|
-
console.log(
|
|
941
|
+
console.log(formatLogMessage('debug', `${INTERACTION_TAG} Interaction simulation failed for ${currentUrl}: ${interactionErr.message}`));
|
|
1042
942
|
}
|
|
1043
943
|
// Don't throw - interaction failures shouldn't break the main scan
|
|
1044
944
|
}
|
|
@@ -1097,13 +997,14 @@ async function performPageInteraction(page, currentUrl, options = {}, forceDebug
|
|
|
1097
997
|
function createInteractionConfig(url, siteConfig = {}) {
|
|
1098
998
|
try {
|
|
1099
999
|
const hostname = new URL(url).hostname.toLowerCase();
|
|
1100
|
-
|
|
1101
|
-
// Site-specific interaction patterns
|
|
1000
|
+
|
|
1001
|
+
// Site-specific interaction patterns. mouseMovements is intentionally
|
|
1002
|
+
// unset by default so intensity drives the count downstream — the prior
|
|
1003
|
+
// hardcoded `mouseMovements: 3` silently masked the HIGH intensity's
|
|
1004
|
+
// intended 5 movements when news/blog sites flipped intensity='high'.
|
|
1102
1005
|
const config = {
|
|
1103
|
-
mouseMovements: 3,
|
|
1104
1006
|
includeScrolling: true,
|
|
1105
1007
|
includeElementClicks: false,
|
|
1106
|
-
includeTyping: false,
|
|
1107
1008
|
duration: 2000,
|
|
1108
1009
|
intensity: 'medium'
|
|
1109
1010
|
};
|
|
@@ -1118,7 +1019,7 @@ function createInteractionConfig(url, siteConfig = {}) {
|
|
|
1118
1019
|
config.intensity = 'low';
|
|
1119
1020
|
} else if (hostname.includes('social') || hostname.includes('forum')) {
|
|
1120
1021
|
config.includeScrolling = true;
|
|
1121
|
-
config.mouseMovements = 4;
|
|
1022
|
+
config.mouseMovements = 4; // Explicit override — distinct from intensity
|
|
1122
1023
|
config.intensity = 'medium';
|
|
1123
1024
|
config.duration = SITE_DURATIONS.SOCIAL_FORUM;
|
|
1124
1025
|
}
|
|
@@ -1139,12 +1040,11 @@ function createInteractionConfig(url, siteConfig = {}) {
|
|
|
1139
1040
|
|
|
1140
1041
|
return config;
|
|
1141
1042
|
} catch (urlErr) {
|
|
1142
|
-
// Return default config if URL parsing fails
|
|
1043
|
+
// Return default config if URL parsing fails — mouseMovements unset so
|
|
1044
|
+
// the intensity-driven default applies in performPageInteraction.
|
|
1143
1045
|
return {
|
|
1144
|
-
mouseMovements: INTENSITY_SETTINGS.MEDIUM.movements,
|
|
1145
1046
|
includeScrolling: true,
|
|
1146
1047
|
includeElementClicks: false,
|
|
1147
|
-
includeTyping: false,
|
|
1148
1048
|
duration: TIMING.DEFAULT_INTERACTION_DURATION,
|
|
1149
1049
|
intensity: 'medium'
|
|
1150
1050
|
};
|
|
@@ -1167,7 +1067,6 @@ function createInteractionConfig(url, siteConfig = {}) {
|
|
|
1167
1067
|
* humanLikeMouseMove: Realistic mouse movement with curves
|
|
1168
1068
|
* simulateScrolling: Smooth scrolling simulation
|
|
1169
1069
|
* interactWithElements: Safe element clicking
|
|
1170
|
-
* simulateTyping: Human-like typing with mistakes
|
|
1171
1070
|
* generateRandomCoordinates: Smart coordinate generation
|
|
1172
1071
|
*/
|
|
1173
1072
|
|
|
@@ -1192,13 +1091,11 @@ module.exports = {
|
|
|
1192
1091
|
// Main interaction functions
|
|
1193
1092
|
performPageInteraction,
|
|
1194
1093
|
createInteractionConfig,
|
|
1195
|
-
|
|
1196
|
-
cleanupInteractionMemory,
|
|
1094
|
+
getViewport,
|
|
1197
1095
|
// Component functions for custom implementations
|
|
1198
1096
|
humanLikeMouseMove,
|
|
1199
1097
|
simulateScrolling,
|
|
1200
1098
|
interactWithElements,
|
|
1201
1099
|
performContentClicks,
|
|
1202
|
-
simulateTyping,
|
|
1203
1100
|
generateRandomCoordinates
|
|
1204
1101
|
};
|