#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Timestamp: "2025-10-10 00:50:44 (ywatanabe)"
# File: /home/ywatanabe/proj/scitex_repo/src/scitex/browser/stealth/StealthManager.py
# ----------------------------------------
from __future__ import annotations
import os
__FILE__ = "./src/scitex/browser/stealth/StealthManager.py"
__DIR__ = os.path.dirname(__FILE__)
# ----------------------------------------
__FILE__ = __file__
import asyncio
import random
from playwright.async_api import Browser, BrowserContext, Page
import scitex_logging as logging
logger = logging.getLogger(__name__)
# User Agent(Old) Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36
# WebDriver(New) missing (passed)
# WebDriver Advanced passed
# Chrome(New) present (passed)
# Permissions(New) prompt
# Plugins Length(Old) 5
# Plugins is of type PluginArray passed
# Languages(Old) en-US,en,ja
# WebGL Vendor Google Inc. (AMD)
# WebGL Renderer ANGLE (AMD, AMD Radeon(TM) Graphics (0x00001636) Direct3D11 vs_5_0 ps_5_0, D3D11)
# Broken Image Dimensions 16x16
[docs]
class StealthManager:
[docs]
def __init__(
self,
viewport_size: tuple = None,
spoof_dimension: bool = False,
):
self.name = self.__class__.__name__
self.viewport_size = viewport_size
self.spoof_dimension = spoof_dimension
[docs]
def get_random_user_agent(self) -> str:
user_agents = [
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
# "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
# "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
# "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
# "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
# "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
# "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
# "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
]
user_agent = random.choice(user_agents)
logger.debug(f"{self.name}: User Agent randomly selected: {user_agent}")
return user_agent
[docs]
def get_random_viewport(self) -> dict:
if self.viewport_size:
viewport = {
"width": self.viewport_size[0],
"height": self.viewport_size[1],
}
logger.debug(
f"{self.name}: Viewport defined as specified in Stealth Manager initiation: {viewport}"
)
return viewport
if self.spoof_dimension:
# viewport = {"width": 1, "height": 1}
viewport = {"width": 1920, "height": 1080}
logger.debug(
f"{self.name}: Viewport defined as spoof_dimension passed during Stealth Manager initiation: {viewport}"
)
return viewport
else:
viewport = random.choice(
[
{"width": 1920, "height": 1080},
{"width": 1366, "height": 768},
{"width": 1440, "height": 900},
{"width": 1280, "height": 720},
]
)
logger.debug(f"{self.name}: Viewport randomly selected: {viewport}")
return viewport
[docs]
def get_stealth_options(self) -> dict:
return {
"viewport": self.get_random_viewport(),
"user_agent": self.get_random_user_agent(),
"extra_http_headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br, zstd",
"Cache-Control": "max-age=0",
"Sec-Ch-Ua": '"Google Chrome";v="132", "Chromium";v="132", "Not_A Brand";v="24"',
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": '"Windows"',
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
"Referer": "https://www.google.com/",
},
"ignore_https_errors": True,
"java_script_enabled": True,
}
[docs]
def get_stealth_options_additional(self) -> list:
stealth_args = [
# Core security and sandbox
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-dev-shm-usage",
# Critical automation detection bypass
"--disable-blink-features=AutomationControlled",
"--disable-features=UserAgentClientHint",
"--disable-features=WebRtcHideLocalIpsWithMdns",
"--disable-features=VizDisplayCompositor",
"--disable-features=TranslateUI",
"--disable-features=Translate",
"--disable-features=MediaRouter",
"--disable-features=OptimizationHints",
"--disable-features=AudioServiceOutOfProcess",
"--disable-features=VizServiceSharingEnabled",
# Enhanced fingerprinting resistance
"--disable-web-security",
"--disable-site-isolation-trials",
"--disable-cross-domain-blocking",
"--disable-features=CrossOriginOpenerPolicy",
"--disable-features=DocumentPolicy",
"--disable-features=OriginPolicy",
# Network and connectivity
"--disable-background-networking",
"--disable-client-side-phishing-detection",
"--disable-component-update",
"--disable-domain-reliability",
"--disable-background-mode",
"--disable-ipc-flooding-protection",
# Browser behavior normalization
"--disable-sync",
"--disable-translate",
"--disable-default-apps",
"--enable-extensions",
"--no-first-run",
"--no-default-browser-check",
"--disable-infobars",
"--disable-notifications",
# POPUP BLOCKING - Add these lines
"--block-new-web-contents",
"--disable-popup-blocking",
"--suppress-message-center-popups",
"--disable-session-crashed-bubble",
"--disable-features=UserAgentClientHint,TranslateSubFrames,AutofillServerCommunication",
# Performance and timing
"--disable-background-timer-throttling",
"--disable-backgrounding-occluded-windows",
"--disable-renderer-backgrounding",
"--disable-hang-monitor",
"--disable-plugins-discovery",
"--disable-field-trial-config",
# Media and hardware access
"--use-fake-ui-for-media-stream",
"--use-fake-device-for-media-stream",
"--autoplay-policy=user-gesture-required",
"--disable-audio-output",
# Logging and debugging
"--disable-logging",
"--disable-gpu-logging",
"--disable-dev-shm-usage",
"--disable-renderer-code-integrity",
# Memory optimization
"--memory-pressure-off",
"--max_old_space_size=4096",
"--disable-low-res-tiling",
"--disable-partial-raster",
"--disable-checker-imaging",
# TLS/SSL improvements
"--ignore-certificate-errors",
"--ignore-ssl-errors",
"--ignore-certificate-errors-spki-list",
"--disable-web-security",
# Additional anti-detection
"--disable-features=VizHitTestSurfaceLayer",
"--disable-features=TranslateSubFrames",
"--disable-search-engine-choice-screen",
"--disable-features=PrivacySandboxSettings4",
"--disable-features=AutofillServerCommunication",
"--enable-extensions",
]
# Apply window size and position based on mode
if self.spoof_dimension:
# 1x1 window completely off-screen for true invisibility
stealth_args.extend(["--window-size=1,1", "--window-position=0,0"])
logger.debug(
f"{self.name}: Invisible mode: Window set to 1x1 at position 0,0 (off-screen)"
)
else:
# Standard window or custom size
if self.viewport_size:
stealth_args.append(
f"--window-size={self.viewport_size[0]},{self.viewport_size[1]}"
)
else:
stealth_args.append("--window-size=1920,1080")
if self.spoof_dimension:
config_desc = "Invisible (1x1)"
elif self.viewport_size:
config_desc = f"{self.viewport_size[0]}x{self.viewport_size[1]}"
else:
config_desc = "Default (1920x1080)"
logger.debug(f"{self.name}: Browser window configuration: {config_desc}")
return stealth_args
[docs]
async def add_human_behavior_async(self, page: Page):
"""Add human-like behavior patterns to avoid detection."""
# Random delay before starting interactions
delay = random.uniform(2, 5)
logger.debug(f"{self.name}: Adding human behavior delay: {delay:.2f} seconds")
await asyncio.sleep(delay)
# Simulate scrolling behavior
try:
await page.evaluate(
"""
window.scrollTo({
top: Math.random() * 500,
behavior: 'smooth'
});
"""
)
await asyncio.sleep(random.uniform(1, 3))
except Exception as e:
logger.debug(f"{self.name}: Human behavior simulation failed: {e}")
[docs]
async def handle_cloudflare_challenge_async(self, page: Page, max_wait: int = 45):
"""Enhanced Cloudflare challenge detection and handling."""
logger.debug(f"{self.name}: Checking for Cloudflare challenge...")
cloudflare_indicators = [
"Just a moment",
"Checking your browser",
"DDoS protection by Cloudflare",
"Cloudflare Ray ID",
"cf-browser-verification",
"Please wait while we verify you're a human",
"Verify you are human",
"Security check",
"Browser verification",
"cf-challenge-running",
]
try:
# First check if we're on a Cloudflare challenge page
page_content = await page.content()
title = await page.title()
is_challenge = any(
indicator.lower() in page_content.lower()
or indicator.lower() in title.lower()
for indicator in cloudflare_indicators
)
if not is_challenge:
logger.debug(f"{self.name}: No Cloudflare challenge detected")
return True
logger.debug(
f"{self.name}: Cloudflare challenge detected, waiting for completion..."
)
# Add human-like behavior during challenge
await self.add_human_behavior_async(page)
# Wait for challenge completion with multiple conditions
await page.wait_for_function(
"""
() => {
const content = document.documentElement.innerText.toLowerCase();
const title = document.title.toLowerCase();
// Challenge completion indicators
const challengeComplete = !content.includes('just a moment') &&
!content.includes('checking your browser') &&
!content.includes('ddos protection') &&
!content.includes('please wait') &&
!content.includes('verify you are human') &&
!title.includes('just a moment');
// Also check if we've been redirected or if the URL changed
const urlChanged = window.location.href !== window.initialUrl;
window.initialUrl = window.initialUrl || window.location.href;
return challengeComplete || urlChanged;
}
""",
timeout=max_wait * 1000,
)
# Additional wait to ensure page is fully loaded
await asyncio.sleep(random.uniform(2, 4))
logger.debug(f"{self.name}: Cloudflare challenge passed successfully")
return True
except Exception as e:
logger.warning(
f"{self.name}: Cloudflare challenge handling timeout or error: {e}"
)
# Try to detect if we're still on challenge page
try:
final_content = await page.content()
still_challenged = any(
indicator.lower() in final_content.lower()
for indicator in cloudflare_indicators
)
if still_challenged:
logger.error(
f"{self.name}: Still on Cloudflare challenge page after timeout"
)
return False
else:
logger.debug(
f"{self.name}: Challenge may have completed despite timeout"
)
return True
except:
return False
[docs]
def get_init_script(self) -> str:
return """
(() => {
'use strict';
// === CORE WEBDRIVER DETECTION REMOVAL ===
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined,
configurable: true
});
// Remove all automation-related properties
const automationProps = [
'webdriver', '__driver_evaluate', '__webdriver_evaluate', '__selenium_evaluate',
'__fxdriver_evaluate', '__driver_unwrapped', '__webdriver_unwrapped',
'__selenium_unwrapped', '__fxdriver_unwrapped', '__webdriver_script_function',
'__webdriver_script_func', '__webdriver_script_fn', '__fxdriver_script_fn',
'__selenium_script_fn', '__webdriver_func', '__webdriver_fn', '__$webdriverAsyncExecutor',
'__lastWatirAlert', '__lastWatirConfirm', '__lastWatirPrompt', '_WEBDRIVER_ELEM_CACHE',
'ChromeDriverw', 'driver-evaluate', 'webdriver-evaluate', 'selenium-evaluate',
'webdriverCommand', 'webdriver-evaluate-response', '__webdriverFunc', '__webdriver_script_func',
'__$webdriverAsyncExecutor', '$chrome_asyncScriptInfo', '$cdc_asdjflasutopfhvcZLmcfl_'
];
automationProps.forEach(prop => {
try {
delete window[prop];
delete document[prop];
delete navigator[prop];
} catch (e) {}
});
// === NAVIGATOR PROPERTIES ===
// Mock realistic Chrome object with proper methods
if (!window.chrome || Object.getPrototypeOf(window.chrome) === Object.prototype) {
window.chrome = {
app: {
isInstalled: false,
InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' },
RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' }
},
runtime: {
onConnect: null,
onMessage: null,
onConnectExternal: null,
onMessageExternal: null,
connect: () => {},
sendMessage: () => {},
getManifest: () => ({ name: 'Chrome', version: '132.0.6834.59' }),
getURL: (path) => 'chrome-extension://invalid/' + path
},
webstore: {
onInstallStageChanged: null,
onDownloadProgress: null,
install: () => {}
},
csi: () => ({ pageT: Math.random() * 1000, startE: Math.random() * 1000 }),
loadTimes: () => ({
requestTime: performance.now() / 1000,
startLoadTime: performance.now() / 1000,
commitLoadTime: performance.now() / 1000,
finishDocumentLoadTime: performance.now() / 1000,
finishLoadTime: performance.now() / 1000,
firstPaintTime: performance.now() / 1000,
firstPaintAfterLoadTime: 0,
navigationType: 'Other'
})
};
// Make chrome object non-enumerable to match real Chrome
Object.defineProperty(window, 'chrome', {
value: window.chrome,
writable: false,
enumerable: false,
configurable: false
});
}
// === LANGUAGE AND LOCALE ===
Object.defineProperty(navigator, 'languages', {
get: () => ['en-US', 'en'],
configurable: true
});
Object.defineProperty(navigator, 'language', {
get: () => 'en-US',
configurable: true
});
// === REALISTIC PLUGINS ===
const mockPlugins = [
{
0: { type: "application/x-google-chrome-pdf", suffixes: "pdf", description: "Portable Document Format", enabledPlugin: null },
description: "Portable Document Format",
filename: "internal-pdf-viewer",
length: 1,
name: "Chrome PDF Plugin"
},
{
0: { type: "application/pdf", suffixes: "pdf", description: "Portable Document Format", enabledPlugin: null },
description: "Portable Document Format",
filename: "mhjfbmdgcfjbbpaeojofohoefgiehjai",
length: 1,
name: "Chrome PDF Viewer"
},
{
0: { type: "application/x-nacl", suffixes: "", description: "Native Client Executable", enabledPlugin: null },
1: { type: "application/x-pnacl", suffixes: "", description: "Portable Native Client Executable", enabledPlugin: null },
description: "Native Client",
filename: "internal-nacl-plugin",
length: 2,
name: "Native Client"
}
];
Object.defineProperty(navigator, 'plugins', {
get: () => mockPlugins,
configurable: true
});
// === HARDWARE CONCURRENCY ===
Object.defineProperty(navigator, 'hardwareConcurrency', {
get: () => Math.max(2, Math.min(16, Math.floor(Math.random() * 8) + 4)),
configurable: true
});
// === PERMISSIONS API ===
if (navigator.permissions && navigator.permissions.query) {
const originalQuery = navigator.permissions.query.bind(navigator.permissions);
navigator.permissions.query = async (parameters) => {
const permission = parameters.name;
if (permission === 'notifications') {
return Promise.resolve({ state: 'default', onchange: null });
}
if (permission === 'geolocation') {
return Promise.resolve({ state: 'prompt', onchange: null });
}
try {
return await originalQuery(parameters);
} catch (e) {
return Promise.resolve({ state: 'prompt', onchange: null });
}
};
}
// === Camvas FINGERPRINTING PROTECTION ===
const getImageData = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function(type) {
const shift = Math.floor(Math.random() * 10) - 5;
const originalImageData = getImageData.apply(this, arguments);
return originalImageData;
};
// === WEBGL FINGERPRINTING PROTECTION ===
const glContexts = ['webgl', 'webgl2', 'experimental-webgl', 'experimental-webgl2'];
glContexts.forEach(contextType => {
try {
const canvas = document.createElement('canvas');
const gl = canvas.getContext(contextType);
if (gl) {
const getParameter = gl.getParameter.bind(gl);
gl.getParameter = function(parameter) {
// Vendor and renderer spoofing
if (parameter === gl.VENDOR || parameter === 37445) {
return 'Intel Inc.';
}
if (parameter === gl.RENDERER || parameter === 37446) {
return 'Intel Iris OpenGL Engine';
}
if (parameter === gl.VERSION) {
return 'OpenGL ES 2.0 (ANGLE 2.1.0.c8ea8ca4eb1a)';
}
if (parameter === gl.SHADING_LANGUAGE_VERSION) {
return 'OpenGL ES GLSL ES 1.0 (ANGLE 2.1.0.c8ea8ca4eb1a)';
}
return getParameter(parameter);
};
}
} catch (e) {}
});
// === CANVAS FINGERPRINTING PROTECTION ===
const getContext = HTMLCanvasElement.prototype.getContext;
HTMLCanvasElement.prototype.getContext = function(contextType, ...args) {
if (contextType === '2d') {
const context = getContext.call(this, contextType, ...args);
if (context) {
const originalFillText = context.fillText;
const originalStrokeText = context.strokeText;
context.fillText = function(...args) {
// Add slight noise to prevent consistent fingerprints
if (args.length >= 3) {
args[1] += Math.random() * 0.01 - 0.005;
args[2] += Math.random() * 0.01 - 0.005;
}
return originalFillText.apply(this, args);
};
context.strokeText = function(...args) {
if (args.length >= 3) {
args[1] += Math.random() * 0.01 - 0.005;
args[2] += Math.random() * 0.01 - 0.005;
}
return originalStrokeText.apply(this, args);
};
}
return context;
}
return getContext.call(this, contextType, ...args);
};
// === TIMEZONE AND LOCALE CONSISTENCY ===
if (Intl && Intl.DateTimeFormat) {
const originalResolvedOptions = Intl.DateTimeFormat.prototype.resolvedOptions;
Intl.DateTimeFormat.prototype.resolvedOptions = function() {
const options = originalResolvedOptions.call(this);
options.timeZone = 'America/New_York'; // Consistent timezone
return options;
};
}
// === SCREEN PROPERTIES ===
const screenProps = {
width: 1920,
height: 1080,
availWidth: 1920,
availHeight: 1040,
colorDepth: 24,
pixelDepth: 24,
orientation: {
angle: 0,
type: 'landscape-primary'
}
};
Object.keys(screenProps).forEach(prop => {
if (prop !== 'orientation') {
Object.defineProperty(screen, prop, {
get: () => screenProps[prop],
configurable: true
});
}
});
// === PREVENT TIMING ATTACKS ===
const originalNow = performance.now;
performance.now = function() {
return originalNow.call(this) + Math.random() * 0.1;
};
// === MEMORY INFO SPOOFING ===
if (performance.memory) {
Object.defineProperty(performance, 'memory', {
get: () => ({
jsHeapSizeLimit: 4294705152,
totalJSHeapSize: Math.floor(Math.random() * 50000000) + 10000000,
usedJSHeapSize: Math.floor(Math.random() * 30000000) + 5000000
}),
configurable: true
});
}
// === PREVENT IFRAME DETECTION ===
Object.defineProperty(window, 'top', {
get: () => window,
configurable: true
});
Object.defineProperty(window, 'parent', {
get: () => window,
configurable: true
});
// === CONSOLE CLEANING ===
const originalConsole = { ...console };
const cleanMethods = ['debug', 'log', 'info', 'warn', 'error'];
cleanMethods.forEach(method => {
console[method] = function(...args) {
const text = args.join(' ').toLowerCase();
if (text.includes('devtools') || text.includes('automation') ||
text.includes('webdriver') || text.includes('selenium') ||
text.includes('playwright') || text.includes('puppeteer')) {
return;
}
return originalConsole[method](...args);
};
});
// === MOUSE MOVEMENT SIMULATION ===
let mouseActivity = Date.now();
document.addEventListener('mousemove', () => {
mouseActivity = Date.now();
}, true);
// Simulate natural mouse movements
setInterval(() => {
if (Date.now() - mouseActivity > 30000) {
const event = new MouseEvent('mousemove', {
view: window,
bubbles: true,
cancelable: true,
clientX: Math.random() * window.innerWidth,
clientY: Math.random() * window.innerHeight
});
document.dispatchEvent(event);
}
}, 30000 + Math.random() * 15000);
// === FINAL CLEANUP ===
// Remove any remaining automation traces
delete window.cdc_adoQpoasnfa76pfcZLmcfl_;
delete window.$cdc_asdjflasutopfhvcZLmcfl_;
delete window.$chrome_asyncScriptInfo;
delete window.__$webdriverAsyncExecutor;
// Freeze important objects to prevent modification
try {
Object.freeze(navigator);
Object.freeze(screen);
} catch (e) {}
})();
"""
[docs]
def get_dimension_spoofing_script(self) -> str:
"""
Generate comprehensive JavaScript dimension spoofing script for invisible browser mode.
This creates a dual-layer window configuration:
- Physical window: 1x1 pixel (invisible to user)
- Reported dimensions: 1920x1080 (natural desktop size for bot detection)
The script is bulletproof and handles all dimension-related APIs that
bot detectors commonly check.
"""
logger.debug(
f"{self.name}: stealth_manager.get_dimension_spoofing_script called."
)
if not self.spoof_dimension:
return ""
return """
(() => {
// Target dimensions to report to JavaScript (natural desktop)
const TARGET_WINDOW_WIDTH = 1920;
const TARGET_WINDOW_HEIGHT = 1080;
const TARGET_SCREEN_WIDTH = 1920;
const TARGET_SCREEN_HEIGHT = 1080;
const TARGET_AVAILABLE_WIDTH = 1920;
const TARGET_AVAILABLE_HEIGHT = 1040; // Account for taskbar
// === WINDOW DIMENSIONS ===
// Override all window size properties
Object.defineProperty(window, 'innerWidth', {
get: () => TARGET_WINDOW_WIDTH,
configurable: true
});
Object.defineProperty(window, 'innerHeight', {
get: () => TARGET_WINDOW_HEIGHT,
configurable: true
});
Object.defineProperty(window, 'outerWidth', {
get: () => TARGET_WINDOW_WIDTH,
configurable: true
});
Object.defineProperty(window, 'outerHeight', {
get: () => TARGET_WINDOW_HEIGHT + 100, // Account for browser chrome
configurable: true
});
// Override client dimensions (commonly checked by bot detectors)
if (document.documentElement) {
Object.defineProperty(document.documentElement, 'clientWidth', {
get: () => TARGET_WINDOW_WIDTH,
configurable: true
});
Object.defineProperty(document.documentElement, 'clientHeight', {
get: () => TARGET_WINDOW_HEIGHT,
configurable: true
});
}
// === SCREEN DIMENSIONS ===
// Override all screen properties
Object.defineProperty(window.screen, 'width', {
get: () => TARGET_SCREEN_WIDTH,
configurable: true
});
Object.defineProperty(window.screen, 'height', {
get: () => TARGET_SCREEN_HEIGHT,
configurable: true
});
Object.defineProperty(window.screen, 'availWidth', {
get: () => TARGET_AVAILABLE_WIDTH,
configurable: true
});
Object.defineProperty(window.screen, 'availHeight', {
get: () => TARGET_AVAILABLE_HEIGHT,
configurable: true
});
// === VIEWPORT AND VISUAL DIMENSIONS ===
// Override visual viewport (modern API)
if (window.visualViewport) {
Object.defineProperty(window.visualViewport, 'width', {
get: () => TARGET_WINDOW_WIDTH,
configurable: true
});
Object.defineProperty(window.visualViewport, 'height', {
get: () => TARGET_WINDOW_HEIGHT,
configurable: true
});
}
// === DOCUMENT DIMENSIONS ===
// Override document element dimensions (wait for DOM to be ready)
const overrideDocumentDimensions = () => {
if (document.documentElement) {
Object.defineProperty(document.documentElement, 'clientWidth', {
get: () => TARGET_WINDOW_WIDTH,
configurable: true
});
Object.defineProperty(document.documentElement, 'clientHeight', {
get: () => TARGET_WINDOW_HEIGHT,
configurable: true
});
Object.defineProperty(document.documentElement, 'offsetWidth', {
get: () => TARGET_WINDOW_WIDTH,
configurable: true
});
Object.defineProperty(document.documentElement, 'offsetHeight', {
get: () => TARGET_WINDOW_HEIGHT,
configurable: true
});
Object.defineProperty(document.documentElement, 'scrollWidth', {
get: () => TARGET_WINDOW_WIDTH,
configurable: true
});
Object.defineProperty(document.documentElement, 'scrollHeight', {
get: () => TARGET_WINDOW_HEIGHT,
configurable: true
});
}
if (document.body) {
Object.defineProperty(document.body, 'clientWidth', {
get: () => TARGET_WINDOW_WIDTH,
configurable: true
});
Object.defineProperty(document.body, 'clientHeight', {
get: () => TARGET_WINDOW_HEIGHT,
configurable: true
});
}
};
// Apply immediately if DOM is ready, otherwise wait
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', overrideDocumentDimensions);
} else {
overrideDocumentDimensions();
}
// === MEDIA QUERIES ===
// Override matchMedia for responsive design queries
const originalMatchMedia = window.matchMedia;
window.matchMedia = function(query) {
const result = originalMatchMedia.call(this, query);
// Override common responsive breakpoints based on our spoofed dimensions
if (query.includes('max-width')) {
const maxWidth = parseInt(query.match(/max-width:\\s*(\d+)px/)?.[1] || '0');
if (maxWidth < TARGET_WINDOW_WIDTH) {
Object.defineProperty(result, 'matches', { get: () => false });
}
}
if (query.includes('min-width')) {
const minWidth = parseInt(query.match(/min-width:\\s*(\d+)px/)?.[1] || '0');
if (minWidth <= TARGET_WINDOW_WIDTH) {
Object.defineProperty(result, 'matches', { get: () => true });
}
}
return result;
};
// === EVENT HANDLING ===
// Override resize events to maintain consistency
const originalAddEventListener = window.addEventListener;
window.addEventListener = function(type, listener, options) {
if (type === 'resize') {
// Intercept resize events and provide spoofed dimensions
const wrappedListener = function(event) {
// Create a mock resize event with spoofed dimensions
const mockEvent = new Event('resize');
Object.defineProperty(mockEvent, 'target', {
value: {
innerWidth: TARGET_WINDOW_WIDTH,
innerHeight: TARGET_WINDOW_HEIGHT
}
});
return listener.call(this, mockEvent);
};
return originalAddEventListener.call(this, type, wrappedListener, options);
}
return originalAddEventListener.call(this, type, listener, options);
};
})();
"""
[docs]
async def human_delay_async(self, min_ms: int = 1000, max_ms: int = 3000):
delay = random.randint(min_ms, max_ms)
await asyncio.sleep(delay / 1000)
[docs]
async def human_click_async(self, page: Page, element):
await element.hover()
await self.human_delay_async(200, 500)
await element.click()
[docs]
async def human_mouse_move_async(self, page: Page):
await page.mouse.move(random.randint(100, 800), random.randint(100, 600))
[docs]
async def human_type_async(self, page: Page, selector: str, text: str):
element = page.locator(selector)
await element.click()
for char in text:
await element.type(char)
await self.human_delay_async(50, 200)
def main(args):
"""Demonstrate StealthManager functionality."""
import asyncio
from playwright.async_api import async_playwright
async def demo():
stealth_manager = StealthManager()
async with async_playwright() as p:
browser = await p.chromium.launch(
headless=False,
args=stealth_manager.get_stealth_options_additional(),
)
stealth_options = stealth_manager.get_stealth_options()
context = await browser.new_context(**stealth_options)
await context.add_init_script(stealth_manager.get_init_script())
page = await context.new_page()
await page.goto("https://bot.sannysoft.com/", timeout=30000)
await stealth_manager.human_delay_async(2000, 3000)
await page.screenshot(path="/tmp/stealth_test.png")
print("✓ Stealth test complete: /tmp/stealth_test.png")
await browser.close()
asyncio.run(demo())
return 0
def parse_args():
"""Parse command line arguments."""
import argparse
parser = argparse.ArgumentParser(description="StealthManager demo")
return parser.parse_args()
def run_main() -> None:
"""Initialize scitex framework, run main function, and cleanup."""
global CONFIG, CC, sys, plt, rng
import sys
import matplotlib.pyplot as plt
import scitex as stx
args = parse_args()
CONFIG, sys.stdout, sys.stderr, plt, CC, rng = stx.session.start(
sys,
plt,
args=args,
file=__FILE__,
sdir_suffix=None,
verbose=False,
agg=True,
)
exit_status = main(args)
stx.session.close(
CONFIG,
verbose=False,
notify=False,
message="",
exit_status=exit_status,
)
if __name__ == "__main__":
run_main()
# python -m scitex_browser.stealth.StealthManager
# EOF