UNPKG

@fanboynz/network-scanner

Version:

A Puppeteer-based network scanner for analyzing web traffic, generating adblock filter rules, and identifying third-party requests. Features include fingerprint spoofing, Cloudflare bypass, content analysis with curl/grep, and multiple output formats.

259 lines (228 loc) 8.71 kB
// === Ghost Cursor Module === // Optional wrapper around the ghost-cursor npm package for advanced Bezier-based mouse movements. // Falls back gracefully to built-in interaction.js mouse if ghost-cursor is not installed. // // USAGE (JSON config): // "cursor_mode": "ghost" Enable ghost-cursor for this site // "ghost_cursor_speed": 1.5 Movement speed multiplier (default: 1.0) // "ghost_cursor_hesitate": 100 Delay (ms) before clicking (default: 50) // "ghost_cursor_overshoot": 500 Max overshoot distance in px (default: auto) // // USAGE (CLI): // --ghost-cursor Enable ghost-cursor globally // // INSTALL: // npm install ghost-cursor (optional dependency) const { formatLogMessage } = require('./colorize'); let ghostCursorModule = null; let ghostCursorAvailable = false; // Attempt to load ghost-cursor at module init — optional dependency try { ghostCursorModule = require('ghost-cursor'); ghostCursorAvailable = true; } catch { // ghost-cursor not installed — this is fine, built-in mouse will be used } /** * Check if ghost-cursor is available * @returns {boolean} */ function isGhostCursorAvailable() { return ghostCursorAvailable; } /** * Create a ghost-cursor instance bound to a Puppeteer page * @param {import('puppeteer').Page} page - Puppeteer page instance * @param {object} options - Configuration options * @param {boolean} options.forceDebug - Enable debug logging * @param {number} options.startX - Starting X coordinate (default: 0) * @param {number} options.startY - Starting Y coordinate (default: 0) * @returns {object|null} Ghost cursor instance or null if unavailable */ function createGhostCursor(page, options = {}) { if (!ghostCursorAvailable) { return null; } const { forceDebug, startX = 0, startY = 0 } = options; try { const cursor = ghostCursorModule.createCursor(page, { x: startX, y: startY }); if (forceDebug) { console.log(formatLogMessage('debug', '[ghost-cursor] Cursor instance created')); } return cursor; } catch (err) { if (forceDebug) { console.log(formatLogMessage('debug', `[ghost-cursor] Failed to create cursor: ${err.message}`)); } return null; } } /** * Move cursor to coordinates using ghost-cursor Bezier paths * Drop-in replacement for humanLikeMouseMove when ghost-cursor is active * * @param {object} cursor - Ghost cursor instance from createGhostCursor() * @param {number} toX - Target X coordinate * @param {number} toY - Target Y coordinate * @param {object} options - Movement options * @param {number} options.moveSpeed - Speed multiplier (default: auto/random) * @param {number} options.moveDelay - Delay after movement in ms (default: 0) * @param {boolean} options.randomizeMoveDelay - Randomize move delay (default: true) * @param {number} options.overshootThreshold - Max overshoot distance in px * @param {boolean} options.forceDebug - Enable debug logging * @returns {Promise<boolean>} true if movement succeeded */ async function ghostMove(cursor, toX, toY, options = {}) { if (!cursor) return false; const { moveSpeed, moveDelay = 0, randomizeMoveDelay = true, overshootThreshold, forceDebug } = options; try { const moveOpts = {}; if (moveSpeed !== undefined) moveOpts.moveSpeed = moveSpeed; if (moveDelay > 0) moveOpts.moveDelay = moveDelay; if (randomizeMoveDelay !== undefined) moveOpts.randomizeMoveDelay = randomizeMoveDelay; if (overshootThreshold !== undefined) moveOpts.overshootThreshold = overshootThreshold; await cursor.moveTo({ x: toX, y: toY }, moveOpts); if (forceDebug) { console.log(formatLogMessage('debug', `[ghost-cursor] Moved to (${Math.round(toX)}, ${Math.round(toY)})`)); } return true; } catch (err) { if (forceDebug) { console.log(formatLogMessage('debug', `[ghost-cursor] Move failed: ${err.message}`)); } return false; } } /** * Click on a CSS selector or coordinates using ghost-cursor * * @param {object} cursor - Ghost cursor instance * @param {string|{x: number, y: number}} target - CSS selector or {x, y} coordinates * @param {object} options - Click options * @param {number} options.hesitate - Delay (ms) before clicking (default: 50) * @param {number} options.waitForClick - Delay (ms) between mousedown/mouseup (default: auto) * @param {number} options.moveDelay - Delay (ms) after moving to target * @param {number} options.paddingPercentage - Click point within element (0=edge, 100=center) * @param {boolean} options.forceDebug - Enable debug logging * @returns {Promise<boolean>} true if click succeeded */ async function ghostClick(cursor, target, options = {}) { if (!cursor) return false; const { hesitate = 50, waitForClick, moveDelay, paddingPercentage, forceDebug } = options; try { const clickOpts = { hesitate }; if (waitForClick !== undefined) clickOpts.waitForClick = waitForClick; if (moveDelay !== undefined) clickOpts.moveDelay = moveDelay; if (paddingPercentage !== undefined) clickOpts.paddingPercentage = paddingPercentage; if (typeof target === 'string') { await cursor.click(target, clickOpts); } else { // For coordinate clicks, move first then use page click await cursor.moveTo(target); // Small hesitation before clicking if (hesitate > 0) { await new Promise(resolve => setTimeout(resolve, hesitate)); } const page = cursor._page || cursor.page; if (page && typeof page.mouse?.click === 'function') { await page.mouse.click(target.x, target.y); } } if (forceDebug) { const label = typeof target === 'string' ? target : `(${Math.round(target.x)}, ${Math.round(target.y)})`; console.log(formatLogMessage('debug', `[ghost-cursor] Clicked ${label}`)); } return true; } catch (err) { if (forceDebug) { console.log(formatLogMessage('debug', `[ghost-cursor] Click failed: ${err.message}`)); } return false; } } /** * Perform a random idle mouse movement using ghost-cursor * * @param {object} cursor - Ghost cursor instance * @param {object} options - Options * @param {boolean} options.forceDebug - Enable debug logging * @returns {Promise<boolean>} true if movement succeeded */ async function ghostRandomMove(cursor, options = {}) { if (!cursor) return false; try { await cursor.randomMove(); if (options.forceDebug) { console.log(formatLogMessage('debug', '[ghost-cursor] Random movement performed')); } return true; } catch (err) { if (options.forceDebug) { console.log(formatLogMessage('debug', `[ghost-cursor] Random move failed: ${err.message}`)); } return false; } } /** * Generate a Bezier path between two points (standalone, no browser needed) * * @param {{x: number, y: number}} from - Start point * @param {{x: number, y: number}} to - End point * @param {object} options - Path options * @param {number} options.spreadOverride - Override curve spread * @param {number} options.moveSpeed - Movement speed * @returns {Array<{x: number, y: number}>|null} Array of path points or null */ function ghostPath(from, to, options = {}) { if (!ghostCursorAvailable || !ghostCursorModule.path) return null; try { return ghostCursorModule.path(from, to, options); } catch { return null; } } /** * Resolve ghost-cursor settings from site config and CLI flags. * Returns null if ghost-cursor should not be used. * * @param {object} siteConfig - Per-site JSON configuration * @param {boolean} globalGhostCursor - CLI --ghost-cursor flag * @param {boolean} forceDebug - Debug logging flag * @returns {object|null} Resolved ghost-cursor options or null */ function resolveGhostCursorConfig(siteConfig, globalGhostCursor, forceDebug) { const enabled = globalGhostCursor || siteConfig.cursor_mode === 'ghost'; if (!enabled) return null; if (!ghostCursorAvailable) { console.warn(formatLogMessage('warn', '[ghost-cursor] cursor_mode "ghost" requested but ghost-cursor package is not installed. Run: npm install ghost-cursor')); return null; } return { moveSpeed: siteConfig.ghost_cursor_speed || undefined, hesitate: siteConfig.ghost_cursor_hesitate ?? 50, overshootThreshold: siteConfig.ghost_cursor_overshoot || undefined, duration: siteConfig.ghost_cursor_duration || siteConfig.interact_duration || 2000, forceDebug }; } module.exports = { isGhostCursorAvailable, createGhostCursor, ghostMove, ghostClick, ghostRandomMove, ghostPath, resolveGhostCursorConfig };