mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-11 09:46:16 +00:00
Summary of implemented anti-detection improvements:
New files created: SecureRandom.ts: Crypto-secure randomization (replaces Math.random()) NaturalMouse.ts: Mouse movements with Bézier curves AntiDetectionScripts.ts: 23 layers of client-side anti-detection Modified files: Browser.ts: Integration of the centralized anti-detection system Humanizer.ts: Complete rewrite with session personalization HumanBehavior.ts: Unique personalization per account creation Config.ts: New ConfigAntiDetection interface 23 active anti-detection protections: WebDriver removal Chrome runtime mocking Canvas noise injection WebGL parameter spoofing Audio fingerprint protection WebRTC IP leak prevention Battery API spoofing Permissions API masking Plugin spoofing Network Information Media Device Protection Speech Synthesis Protection Keyboard Layout Detection and Prevention Timing Attack Prevention Error Stack Sanitization Console Protection Navigator Properties Protection Hardware Concurrency Spoofing Memory Spoofing Connection Information PDF Viewer Detection Automation Detection (Puppeteer, Selenium, Playwright) Timezone/Locale Coherence Behavioral Improvements: Bezier Curves for Mouse Movement (No Straight Lines) Simulated Natural Tremors in Movements Overshoot and Correction Like a Real Human Unique Personality Per Session (Typing Speed, Mouse Precision, Error Rate) Gaussian Distribution for Delays (No Fixed Timing) Fatigue Simulation with Performance Variation Random Thinking Pauses
This commit is contained in:
@@ -3,21 +3,88 @@
|
||||
*
|
||||
* CRITICAL: Microsoft detects bots by analyzing:
|
||||
* 1. Typing speed (instant .fill() = bot, gradual .type() = human)
|
||||
* 2. Mouse movements (no movement = bot, random moves = human)
|
||||
* 2. Mouse movements (linear = bot, Bézier curves = human)
|
||||
* 3. Pauses (fixed delays = bot, variable pauses = human)
|
||||
* 4. Click patterns (force clicks = bot, natural clicks = human)
|
||||
* 5. Session consistency (same patterns = bot farm)
|
||||
*
|
||||
* MAJOR IMPROVEMENTS (v3.5):
|
||||
* - Crypto-secure randomness (not Math.random)
|
||||
* - Bézier curve mouse movements
|
||||
* - Session personality (unique behavior per account)
|
||||
* - Fatigue simulation
|
||||
* - Natural scroll with inertia
|
||||
*
|
||||
* This module ensures account creation is INDISTINGUISHABLE from manual creation.
|
||||
*/
|
||||
|
||||
import type { Page } from 'rebrowser-playwright'
|
||||
import { log } from '../util/notifications/Logger'
|
||||
import { generateMousePath, generateScrollPath } from '../util/security/NaturalMouse'
|
||||
import {
|
||||
humanVariance,
|
||||
secureGaussian,
|
||||
secureRandomBool,
|
||||
secureRandomFloat,
|
||||
secureRandomInt,
|
||||
typingDelay
|
||||
} from '../util/security/SecureRandom'
|
||||
|
||||
/**
|
||||
* Session personality - unique behavior patterns per account creation
|
||||
*/
|
||||
interface CreatorPersonality {
|
||||
typingSpeed: number // 0.6-1.4
|
||||
mousePrecision: number // 0.7-1.3
|
||||
pauseTendency: number // 0.5-1.5
|
||||
errorRate: number // 0-0.08 (typo probability)
|
||||
burstTyping: boolean // Does this person type in bursts?
|
||||
readingSpeed: number // WPM for reading
|
||||
confidenceLevel: number // 0.7-1.3 (affects hesitation)
|
||||
}
|
||||
|
||||
export class HumanBehavior {
|
||||
private page: Page
|
||||
private personality: CreatorPersonality
|
||||
private sessionStart: number
|
||||
private actionCount: number = 0
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
this.sessionStart = Date.now()
|
||||
|
||||
// Generate unique personality for this account creation
|
||||
this.personality = this.generatePersonality()
|
||||
|
||||
log(false, 'CREATOR', `🧠 Session personality: typing=${this.personality.typingSpeed.toFixed(2)}x, ` +
|
||||
`precision=${this.personality.mousePrecision.toFixed(2)}x, ` +
|
||||
`confidence=${this.personality.confidenceLevel.toFixed(2)}`, 'log', 'cyan')
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate unique personality for this session
|
||||
*/
|
||||
private generatePersonality(): CreatorPersonality {
|
||||
return {
|
||||
typingSpeed: secureRandomFloat(0.6, 1.4),
|
||||
mousePrecision: secureRandomFloat(0.7, 1.3),
|
||||
pauseTendency: secureRandomFloat(0.5, 1.5),
|
||||
errorRate: secureRandomFloat(0, 0.08),
|
||||
burstTyping: secureRandomBool(0.3),
|
||||
readingSpeed: secureRandomInt(180, 320),
|
||||
confidenceLevel: secureRandomFloat(0.7, 1.3)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fatigue multiplier based on session duration
|
||||
*/
|
||||
private getFatigueMultiplier(): number {
|
||||
const sessionDuration = Date.now() - this.sessionStart
|
||||
const minutesActive = sessionDuration / 60000
|
||||
|
||||
// Fatigue increases over 30+ minutes
|
||||
return 1 + Math.min(0.4, Math.max(0, (minutesActive - 30) * 0.01))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,17 +96,28 @@ export class HumanBehavior {
|
||||
* @param context Description for logging (optional)
|
||||
*/
|
||||
async humanDelay(minMs: number, maxMs: number, context?: string): Promise<void> {
|
||||
// IMPROVEMENT: Add occasional "thinking" pauses (10% chance of 2x delay)
|
||||
const shouldThink = Math.random() < 0.1
|
||||
const multiplier = shouldThink ? 2 : 1
|
||||
// Use Gaussian distribution centered on mean
|
||||
const mean = (minMs + maxMs) / 2
|
||||
const stdDev = (maxMs - minMs) / 4
|
||||
|
||||
const delay = (Math.random() * (maxMs - minMs) + minMs) * multiplier
|
||||
let delay = secureGaussian(mean, stdDev)
|
||||
|
||||
if (shouldThink && context) {
|
||||
log(false, 'CREATOR', `[${context}] 🤔 Thinking pause (${Math.floor(delay)}ms)`, 'log', 'cyan')
|
||||
// Apply personality and fatigue
|
||||
delay *= this.personality.pauseTendency * this.getFatigueMultiplier()
|
||||
|
||||
// 10% chance of "thinking" pause (2x delay)
|
||||
if (secureRandomBool(0.1)) {
|
||||
delay *= 2
|
||||
if (context) {
|
||||
log(false, 'CREATOR', `[${context}] 🤔 Thinking pause (${Math.floor(delay)}ms)`, 'log', 'cyan')
|
||||
}
|
||||
}
|
||||
|
||||
// Clamp to reasonable bounds
|
||||
delay = Math.max(minMs * 0.5, Math.min(maxMs * 2, delay))
|
||||
|
||||
await this.page.waitForTimeout(Math.floor(delay))
|
||||
this.actionCount++
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,68 +131,88 @@ export class HumanBehavior {
|
||||
async humanType(locator: import('rebrowser-playwright').Locator, text: string, context: string): Promise<void> {
|
||||
// CRITICAL: Clear field first (human would select all + delete)
|
||||
await locator.clear()
|
||||
await this.humanDelay(300, 800, context)
|
||||
|
||||
// CRITICAL: Type character by character with VARIABLE delays
|
||||
// Real humans type at 40-80 WPM = ~150-300ms per character
|
||||
// But with natural variation: some characters faster, some slower
|
||||
await this.humanDelay(200, 600, context)
|
||||
|
||||
log(false, 'CREATOR', `[${context}] ⌨️ Typing: "${text.substring(0, 20)}${text.length > 20 ? '...' : ''}"`, 'log', 'cyan')
|
||||
|
||||
// IMPROVED: Generate per-session typing personality (consistent across field)
|
||||
const typingSpeed = 0.7 + Math.random() * 0.6 // 0.7-1.3x speed multiplier
|
||||
const errorRate = Math.random() * 0.08 // 0-8% error rate
|
||||
const burstTyping = Math.random() < 0.3 // 30% chance of burst typing
|
||||
// Track if we should simulate a typo
|
||||
let typoMade = false
|
||||
const shouldMakeTypo = secureRandomBool(this.personality.errorRate * 3) // Once per field max
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char: string = text[i] as string
|
||||
|
||||
// CRITICAL: Skip if char is somehow undefined (defensive programming)
|
||||
if (!char) continue
|
||||
|
||||
// IMPROVED: More realistic variance based on typing personality
|
||||
// Determine character delay based on type
|
||||
let charDelay: number
|
||||
const isFastKey = /[eatino]/i.test(char)
|
||||
const isSlowKey = /[^a-z]/i.test(char) // Numbers, symbols, etc.
|
||||
const hasTypo = Math.random() < errorRate // Dynamic typo rate
|
||||
const isBurst = burstTyping && i > 0 && Math.random() < 0.4 // Burst typing pattern
|
||||
const isFastKey = /[eatinos]/i.test(char)
|
||||
const isSlowKey = /[^a-z0-9@.]/i.test(char) // Symbols, uppercase
|
||||
const isBurst = this.personality.burstTyping && secureRandomBool(0.3)
|
||||
|
||||
if (hasTypo) {
|
||||
// Typo: pause, backspace, retype
|
||||
charDelay = Math.random() * 500 + 400 // 400-900ms (correcting)
|
||||
log(false, 'CREATOR', `[${context}] 🔄 Typo correction simulation`, 'log', 'gray')
|
||||
} else if (isBurst) {
|
||||
// Burst typing: very fast
|
||||
charDelay = (Math.random() * 50 + 40) * typingSpeed // 40-90ms * speed
|
||||
if (isBurst && i > 0) {
|
||||
// Burst typing: very fast sequence
|
||||
charDelay = typingDelay(30) * this.personality.typingSpeed
|
||||
} else if (isFastKey) {
|
||||
charDelay = (Math.random() * 80 + 70) * typingSpeed // 70-150ms * speed
|
||||
charDelay = typingDelay(60) * this.personality.typingSpeed
|
||||
} else if (isSlowKey) {
|
||||
charDelay = (Math.random() * 250 + 180) * typingSpeed // 180-430ms * speed
|
||||
charDelay = typingDelay(150) * this.personality.typingSpeed
|
||||
} else {
|
||||
charDelay = (Math.random() * 120 + 100) * typingSpeed // 100-220ms * speed
|
||||
charDelay = typingDelay(80) * this.personality.typingSpeed
|
||||
}
|
||||
|
||||
// IMPROVED: Random micro-pauses (thinking)
|
||||
if (Math.random() < 0.05 && i > 0) {
|
||||
const thinkPause = Math.random() * 800 + 500 // 500-1300ms
|
||||
await this.page.waitForTimeout(Math.floor(thinkPause))
|
||||
// Simulate typo (once per field, if enabled)
|
||||
if (!typoMade && shouldMakeTypo && i > 2 && i < text.length - 2 && secureRandomBool(0.15)) {
|
||||
typoMade = true
|
||||
|
||||
// Type wrong character
|
||||
const wrongChar = String.fromCharCode(char.charCodeAt(0) + secureRandomInt(-1, 1))
|
||||
await locator.type(wrongChar, { delay: 0 })
|
||||
await this.page.waitForTimeout(secureRandomInt(200, 500))
|
||||
|
||||
// Pause (realize mistake)
|
||||
await this.page.waitForTimeout(secureRandomInt(300, 800))
|
||||
|
||||
// Backspace
|
||||
await this.page.keyboard.press('Backspace')
|
||||
await this.page.waitForTimeout(secureRandomInt(100, 300))
|
||||
|
||||
log(false, 'CREATOR', `[${context}] 🔄 Typo correction`, 'log', 'gray')
|
||||
}
|
||||
|
||||
await locator.type(char, { delay: 0 }) // Type instantly
|
||||
// Type the character
|
||||
await locator.type(char, { delay: 0 })
|
||||
await this.page.waitForTimeout(Math.floor(charDelay))
|
||||
|
||||
// Occasional micro-pause (human thinking)
|
||||
if (secureRandomBool(0.05) && i > 0) {
|
||||
await this.page.waitForTimeout(secureRandomInt(300, 800))
|
||||
}
|
||||
|
||||
// Burst typing: type next 2-3 chars rapidly
|
||||
if (isBurst && i < text.length - 2) {
|
||||
const burstLen = secureRandomInt(2, 3)
|
||||
for (let j = 0; j < burstLen && i + 1 < text.length; j++) {
|
||||
i++
|
||||
const nextChar = text[i]
|
||||
if (nextChar) {
|
||||
await locator.type(nextChar, { delay: 0 })
|
||||
await this.page.waitForTimeout(secureRandomInt(15, 40))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log(false, 'CREATOR', `[${context}] ✅ Typing completed`, 'log', 'green')
|
||||
|
||||
// IMPROVEMENT: Random pause after typing (human reviewing input)
|
||||
await this.humanDelay(500, 1500, context)
|
||||
// Random pause after typing (human reviewing input)
|
||||
await this.humanDelay(400, 1200, context)
|
||||
}
|
||||
|
||||
/**
|
||||
* CRITICAL: Simulate micro mouse movements and scrolls
|
||||
* Real humans constantly move mouse and scroll while reading/thinking
|
||||
*
|
||||
* IMPROVED: Natural variance per session (not every gesture is identical)
|
||||
* IMPROVED: Uses Bézier curves for natural movement
|
||||
*
|
||||
* @param context Description for logging
|
||||
*/
|
||||
@@ -122,49 +220,69 @@ export class HumanBehavior {
|
||||
try {
|
||||
const gestureNotes: string[] = []
|
||||
|
||||
// IMPROVED: Variable mouse movement probability (not always 60%)
|
||||
const mouseMoveProb = 0.45 + Math.random() * 0.3 // 45-75% chance
|
||||
// Mouse movement probability varies by personality
|
||||
const mouseMoveProb = 0.35 + this.personality.mousePrecision * 0.3
|
||||
|
||||
if (Math.random() < mouseMoveProb) {
|
||||
// IMPROVED: Wider movement range (more natural)
|
||||
const x = Math.floor(Math.random() * 400) + 30 // Random x: 30-430px
|
||||
const y = Math.floor(Math.random() * 300) + 20 // Random y: 20-320px
|
||||
const steps = Math.floor(Math.random() * 8) + 2 // 2-10 steps (variable smoothness)
|
||||
if (secureRandomBool(mouseMoveProb)) {
|
||||
const viewport = this.page.viewportSize()
|
||||
if (viewport) {
|
||||
// Get random target
|
||||
const targetX = secureRandomInt(50, viewport.width - 50)
|
||||
const targetY = secureRandomInt(50, viewport.height - 50)
|
||||
|
||||
await this.page.mouse.move(x, y, { steps }).catch(() => {
|
||||
// Mouse move failed - page may be closed or unavailable
|
||||
})
|
||||
// Generate Bézier curve path
|
||||
const startX = secureRandomInt(100, viewport.width / 2)
|
||||
const startY = secureRandomInt(100, viewport.height / 2)
|
||||
|
||||
gestureNotes.push(`mouse→(${x},${y})`)
|
||||
const path = generateMousePath(
|
||||
{ x: startX, y: startY },
|
||||
{ x: targetX, y: targetY },
|
||||
{
|
||||
speed: this.personality.mousePrecision,
|
||||
overshoot: secureRandomBool(0.2)
|
||||
}
|
||||
)
|
||||
|
||||
// IMPROVED: Sometimes double-move (human overshoots then corrects)
|
||||
if (Math.random() < 0.15) {
|
||||
await this.humanDelay(100, 300, context)
|
||||
const x2 = x + (Math.random() * 40 - 20) // ±20px correction
|
||||
const y2 = y + (Math.random() * 40 - 20)
|
||||
await this.page.mouse.move(x2, y2, { steps: 2 }).catch(() => { })
|
||||
gestureNotes.push(`correct→(${x2},${y2})`)
|
||||
// Execute path
|
||||
for (let i = 0; i < path.points.length; i++) {
|
||||
const point = path.points[i]
|
||||
if (point) {
|
||||
await this.page.mouse.move(point.x, point.y).catch(() => { })
|
||||
}
|
||||
const duration = path.durations[i]
|
||||
if (duration && duration > 0) {
|
||||
await this.page.waitForTimeout(duration).catch(() => { })
|
||||
}
|
||||
}
|
||||
|
||||
gestureNotes.push(`mouse→(${targetX},${targetY})`)
|
||||
}
|
||||
}
|
||||
|
||||
// IMPROVED: Variable scroll probability (not always 30%)
|
||||
const scrollProb = 0.2 + Math.random() * 0.25 // 20-45% chance
|
||||
// Scroll probability
|
||||
const scrollProb = 0.2 + this.personality.pauseTendency * 0.15
|
||||
|
||||
if (Math.random() < scrollProb) {
|
||||
const direction = Math.random() < 0.65 ? 1 : -1 // 65% down, 35% up
|
||||
const distance = Math.floor(Math.random() * 300) + 40 // 40-340px (more variance)
|
||||
const dy = direction * distance
|
||||
if (secureRandomBool(scrollProb)) {
|
||||
const direction = secureRandomBool(0.65) ? 1 : -1
|
||||
const distance = secureRandomInt(40, 250) * direction
|
||||
|
||||
await this.page.mouse.wheel(0, dy).catch(() => {
|
||||
// Scroll failed - page may be closed or unavailable
|
||||
})
|
||||
// Natural scroll with inertia
|
||||
const scrollPath = generateScrollPath(distance, { smooth: true })
|
||||
|
||||
gestureNotes.push(`scroll ${direction > 0 ? '↓' : '↑'} ${distance}px`)
|
||||
for (let i = 0; i < scrollPath.deltas.length; i++) {
|
||||
const delta = scrollPath.deltas[i]
|
||||
if (delta) {
|
||||
await this.page.mouse.wheel(0, delta).catch(() => { })
|
||||
}
|
||||
const duration = scrollPath.durations[i]
|
||||
if (duration && duration > 0) {
|
||||
await this.page.waitForTimeout(duration).catch(() => { })
|
||||
}
|
||||
}
|
||||
|
||||
gestureNotes.push(`scroll ${direction > 0 ? '↓' : '↑'} ${Math.abs(distance)}px`)
|
||||
}
|
||||
|
||||
// IMPROVED: Sometimes NO gesture at all (humans sometimes just stare)
|
||||
// Already handled by caller's random probability
|
||||
|
||||
if (gestureNotes.length > 0) {
|
||||
log(false, 'CREATOR', `[${context}] ${gestureNotes.join(', ')}`, 'log', 'gray')
|
||||
}
|
||||
@@ -177,6 +295,8 @@ export class HumanBehavior {
|
||||
* CRITICAL: Natural click with human behavior
|
||||
* NEVER use { force: true } - it bypasses visibility checks (bot pattern)
|
||||
*
|
||||
* IMPROVED: Uses Bézier curve to move to element
|
||||
*
|
||||
* @param locator Playwright locator (button/link)
|
||||
* @param context Description for logging
|
||||
* @param maxRetries Max click attempts (default: 3)
|
||||
@@ -189,33 +309,57 @@ export class HumanBehavior {
|
||||
): Promise<boolean> {
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
// CRITICAL: Move mouse to element first (real humans do this)
|
||||
// Get element bounding box
|
||||
const box = await locator.boundingBox().catch(() => null)
|
||||
|
||||
if (box) {
|
||||
// Click at random position within element (not always center)
|
||||
const offsetX = Math.random() * box.width * 0.6 + box.width * 0.2 // 20-80% of width
|
||||
const offsetY = Math.random() * box.height * 0.6 + box.height * 0.2 // 20-80% of height
|
||||
// Calculate click position (not always center)
|
||||
const clickX = box.x + box.width * secureRandomFloat(0.25, 0.75)
|
||||
const clickY = box.y + box.height * secureRandomFloat(0.25, 0.75)
|
||||
|
||||
await this.page.mouse.move(
|
||||
box.x + offsetX,
|
||||
box.y + offsetY,
|
||||
{ steps: Math.floor(Math.random() * 3) + 2 } // 2-5 steps
|
||||
).catch(() => { })
|
||||
// Move to element with Bézier curve
|
||||
const viewport = this.page.viewportSize()
|
||||
const startX = viewport ? secureRandomInt(0, viewport.width / 2) : 100
|
||||
const startY = viewport ? secureRandomInt(0, viewport.height / 2) : 100
|
||||
|
||||
await this.humanDelay(100, 300, context) // Pause before clicking
|
||||
const path = generateMousePath(
|
||||
{ x: startX, y: startY },
|
||||
{ x: clickX, y: clickY },
|
||||
{
|
||||
speed: this.personality.mousePrecision,
|
||||
overshoot: secureRandomBool(0.15) // Less overshoot for clicks
|
||||
}
|
||||
)
|
||||
|
||||
// Execute path
|
||||
for (let i = 0; i < path.points.length; i++) {
|
||||
const point = path.points[i]
|
||||
if (point) {
|
||||
await this.page.mouse.move(point.x, point.y).catch(() => { })
|
||||
}
|
||||
const duration = path.durations[i]
|
||||
if (duration && duration > 0) {
|
||||
await this.page.waitForTimeout(duration).catch(() => { })
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-click pause (human aims before clicking)
|
||||
await this.humanDelay(50, 200, context)
|
||||
}
|
||||
|
||||
// NATURAL CLICK: No force (respects visibility/interactability)
|
||||
// Perform click
|
||||
await locator.click({ force: false, timeout: 5000 })
|
||||
|
||||
log(false, 'CREATOR', `[${context}] ✅ Clicked successfully`, 'log', 'green')
|
||||
await this.humanDelay(300, 800, context) // Pause after clicking
|
||||
|
||||
// Post-click pause (human waits for response)
|
||||
await this.humanDelay(250, 700, context)
|
||||
return true
|
||||
|
||||
} catch (error) {
|
||||
if (attempt < maxRetries) {
|
||||
log(false, 'CREATOR', `[${context}] ⚠️ Click failed (attempt ${attempt}/${maxRetries}), retrying...`, 'warn', 'yellow')
|
||||
await this.humanDelay(1000, 2000, context)
|
||||
await this.humanDelay(800, 1800, context)
|
||||
} else {
|
||||
const msg = error instanceof Error ? error.message : String(error)
|
||||
log(false, 'CREATOR', `[${context}] ❌ Click failed after ${maxRetries} attempts: ${msg}`, 'error')
|
||||
@@ -236,15 +380,16 @@ export class HumanBehavior {
|
||||
async readPage(context: string): Promise<void> {
|
||||
log(false, 'CREATOR', `[${context}] 👀 Reading page...`, 'log', 'cyan')
|
||||
|
||||
// Random scroll movements (humans scroll while reading)
|
||||
const scrollCount = Math.floor(Math.random() * 3) + 1 // 1-3 scrolls
|
||||
// Random scroll movements while reading
|
||||
const scrollCount = secureRandomInt(1, 3)
|
||||
for (let i = 0; i < scrollCount; i++) {
|
||||
await this.microGestures(context)
|
||||
await this.humanDelay(800, 2000, context)
|
||||
await this.humanDelay(600, 1800, context)
|
||||
}
|
||||
|
||||
// Final reading pause
|
||||
await this.humanDelay(1500, 3500, context)
|
||||
// Final reading pause (based on personality reading speed)
|
||||
const readTime = (50 / this.personality.readingSpeed) * 60000 // ~50 words
|
||||
await this.humanDelay(readTime * 0.5, readTime * 1.5, context)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,24 +406,49 @@ export class HumanBehavior {
|
||||
optionLocator: import('rebrowser-playwright').Locator,
|
||||
context: string
|
||||
): Promise<boolean> {
|
||||
// STEP 1: Click dropdown button (with human behavior)
|
||||
// STEP 1: Click dropdown button
|
||||
const openSuccess = await this.humanClick(buttonLocator, `${context}_OPEN`)
|
||||
if (!openSuccess) return false
|
||||
|
||||
// STEP 2: Wait for dropdown to open (visual feedback)
|
||||
await this.humanDelay(500, 1200, context)
|
||||
// STEP 2: Wait for dropdown animation
|
||||
await this.humanDelay(400, 1000, context)
|
||||
|
||||
// STEP 3: Move mouse randomly inside dropdown (human reading options)
|
||||
// STEP 3: Move mouse around (reading options)
|
||||
await this.microGestures(context)
|
||||
await this.humanDelay(300, 800, context)
|
||||
await this.humanDelay(200, 600, context)
|
||||
|
||||
// STEP 4: Click selected option (with human behavior)
|
||||
// STEP 4: Click selected option
|
||||
const selectSuccess = await this.humanClick(optionLocator, `${context}_SELECT`)
|
||||
if (!selectSuccess) return false
|
||||
|
||||
// STEP 5: Wait for dropdown to close
|
||||
await this.humanDelay(500, 1200, context)
|
||||
await this.humanDelay(400, 1000, context)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Hesitation pause - simulates uncertainty
|
||||
* Used before important actions where user might reconsider
|
||||
*
|
||||
* @param context Description for logging
|
||||
*/
|
||||
async hesitate(context: string): Promise<void> {
|
||||
if (secureRandomBool(0.4 / this.personality.confidenceLevel)) {
|
||||
const hesitationTime = humanVariance(1500, 0.5) / this.personality.confidenceLevel
|
||||
log(false, 'CREATOR', `[${context}] 🤔 Hesitating...`, 'log', 'cyan')
|
||||
await this.page.waitForTimeout(Math.floor(hesitationTime))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session statistics
|
||||
*/
|
||||
getStats(): { actionCount: number; sessionDurationMs: number; personality: CreatorPersonality } {
|
||||
return {
|
||||
actionCount: this.actionCount,
|
||||
sessionDurationMs: Date.now() - this.sessionStart,
|
||||
personality: this.personality
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user