@fanboynz/network-scanner 2.0.66 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
- * - includeTyping: boolean - Enable typing simulation
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
- // Fine-tune mouse movement behavior for realism vs. speed
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: 5, // Minimum steps for any movement
81
- DEFAULT_STEPS: 15, // Default steps for mouse movement
82
- MAX_STEPS: 30, // Maximum steps to prevent excessive slowness
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
- DEFAULT_CURVE: 0.2, // Default curve intensity (reduced for performance)
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, // CRITICAL: 4x increase to drastically reduce steps
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, // Characters to capture for element text preview
123
- MISTAKE_RATE: 0.02 // Probability of typing mistakes (0.02 = 2% chance)
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: 300, // Small buffer for late-loading async ad scripts
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: 0.3, // 30% chance of random pause during movement
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
- * Gets viewport dimensions with caching for performance
190
- * Caches viewport for 30 seconds to avoid repeated queries
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 getCachedViewport(page) {
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
- cachedViewport = await page.viewport();
205
- lastViewportCheck = now;
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 (Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2) < avoidRadius);
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
- // FIXED: More aggressive step capping to prevent excessive delays
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
- // CRITICAL: Much lower step cap to prevent timeouts
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
- 2, // Min 2 steps instead of 5
363
- Math.min(calculatedSteps, 6) // Max 6 steps instead of 30
374
+ MOUSE_MOVEMENT.MIN_STEPS,
375
+ Math.min(calculatedSteps, MOUSE_MOVEMENT.DEFAULT_STEPS)
364
376
  );
365
377
  }
366
378
 
367
- // CRITICAL: Emergency timeout - never exceed 300ms for mouse movement
368
- const maxTotalTime = 300; // 300ms maximum (was 2000ms)
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 > maxTotalTime) {
371
- actualSteps = Math.floor(maxTotalTime / maxDelay);
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(`[interaction] Page is closed, skipping element interaction`);
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
- await page.waitForSelector('body', { timeout: 1000 });
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(`[interaction] Page not ready for element interaction: ${bodyWaitErr.message}`);
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 getCachedViewport(page);
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 getCachedViewport(page);
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(`[interaction] Content click ${i + 1}/${clicks} at (${targetX}, ${targetY})`);
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 = INTENSITY_SETTINGS.MEDIUM.movements,
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(`[interaction] Page is closed for ${currentUrl}, skipping interaction`);
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 getCachedViewport(page);
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(`[interaction] Starting enhanced interaction simulation for ${hostname} (${intensity} intensity)`);
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 = Math.min(mouseMovements, settings.movements);
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
- const totalDuration = duration * settings.pauseMultiplier;
941
- // CRITICAL: Cap action intervals to prevent long waits
942
- const baseInterval = totalDuration / (actualMovements + (includeScrolling ? settings.scrolls : 0));
943
- const actionInterval = Math.min(baseInterval, 100); // Never wait more than 100ms
944
-
945
- // Start timing ONLY the actual interaction operations
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); // CRITICAL: Much shorter pauses
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 — two strategies for maximum ad script coverage
989
- // 1. Content area clicks: triggers document-level onclick handlers
990
- // (Monetag, similar popunder SDKs that listen on document)
991
- // 2. Element clicks: interacts with specific UI elements
992
- // (ad scripts that attach to specific clickable elements)
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
- // Periodic memory cleanup during interaction
1007
- cleanupInteractionMemory();
1008
-
1009
- // Final hover position
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 humanLikeMouseMove(page, currentPos.x, currentPos.y, finalPos.x, finalPos.y);
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(`[interaction] WARNING: Interaction took ${interactionElapsedTime}ms for ${currentUrl}`);
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(`[interaction] Completed interaction simulation in ${interactionElapsedTime}ms (${actualMovements} movements, ${includeScrolling ? settings.scrolls : 0} scrolls)`);
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(`[interaction] Interaction simulation failed for ${currentUrl}: ${interactionErr.message}`);
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
- getCachedViewport,
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
  };