mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-10 17:26:17 +00:00
feat: Introduire un simulateur de comportement humain pour améliorer la création de comptes
This commit is contained in:
@@ -4,10 +4,12 @@ import * as readline from 'readline'
|
|||||||
import type { BrowserContext, Page } from 'rebrowser-playwright'
|
import type { BrowserContext, Page } from 'rebrowser-playwright'
|
||||||
import { log } from '../util/notifications/Logger'
|
import { log } from '../util/notifications/Logger'
|
||||||
import { DataGenerator } from './DataGenerator'
|
import { DataGenerator } from './DataGenerator'
|
||||||
|
import { HumanBehavior } from './HumanBehavior'
|
||||||
import { CreatedAccount } from './types'
|
import { CreatedAccount } from './types'
|
||||||
|
|
||||||
export class AccountCreator {
|
export class AccountCreator {
|
||||||
private page!: Page
|
private page!: Page
|
||||||
|
private human!: HumanBehavior
|
||||||
private dataGenerator: DataGenerator
|
private dataGenerator: DataGenerator
|
||||||
private referralUrl?: string
|
private referralUrl?: string
|
||||||
private recoveryEmail?: string
|
private recoveryEmail?: string
|
||||||
@@ -27,10 +29,10 @@ export class AccountCreator {
|
|||||||
this.rlClosed = false
|
this.rlClosed = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Human-like delay helper
|
// Human-like delay helper (DEPRECATED - use this.human.humanDelay() instead)
|
||||||
|
// Kept for backward compatibility during migration
|
||||||
private async humanDelay(minMs: number, maxMs: number): Promise<void> {
|
private async humanDelay(minMs: number, maxMs: number): Promise<void> {
|
||||||
const delay = Math.random() * (maxMs - minMs) + minMs
|
await this.human.humanDelay(minMs, maxMs)
|
||||||
await this.page.waitForTimeout(Math.floor(delay))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -618,14 +620,25 @@ export class AccountCreator {
|
|||||||
try {
|
try {
|
||||||
this.page = await context.newPage()
|
this.page = await context.newPage()
|
||||||
|
|
||||||
log(false, 'CREATOR', '🚀 Starting account creation...', 'log', 'cyan')
|
// CRITICAL: Initialize human behavior simulator
|
||||||
|
this.human = new HumanBehavior(this.page)
|
||||||
|
|
||||||
|
log(false, 'CREATOR', '🚀 Starting account creation with enhanced anti-detection...', 'log', 'cyan')
|
||||||
|
|
||||||
// Navigate to signup page
|
// Navigate to signup page
|
||||||
await this.navigateToSignup()
|
await this.navigateToSignup()
|
||||||
|
|
||||||
|
// CRITICAL: Simulate human reading the signup page
|
||||||
|
await this.human.microGestures('SIGNUP_PAGE')
|
||||||
|
await this.humanDelay(500, 1500)
|
||||||
|
|
||||||
// Click "Create account" button
|
// Click "Create account" button
|
||||||
await this.clickCreateAccount()
|
await this.clickCreateAccount()
|
||||||
|
|
||||||
|
// CRITICAL: Simulate human inspecting the email field
|
||||||
|
await this.human.microGestures('EMAIL_FIELD')
|
||||||
|
await this.humanDelay(300, 800)
|
||||||
|
|
||||||
// Generate email and fill it (handles suggestions automatically)
|
// Generate email and fill it (handles suggestions automatically)
|
||||||
const emailResult = await this.generateAndFillEmail(this.autoAccept)
|
const emailResult = await this.generateAndFillEmail(this.autoAccept)
|
||||||
if (!emailResult) {
|
if (!emailResult) {
|
||||||
@@ -635,6 +648,10 @@ export class AccountCreator {
|
|||||||
|
|
||||||
log(false, 'CREATOR', `✅ Email: ${emailResult}`, 'log', 'green')
|
log(false, 'CREATOR', `✅ Email: ${emailResult}`, 'log', 'green')
|
||||||
|
|
||||||
|
// CRITICAL: Simulate human reading password requirements
|
||||||
|
await this.human.microGestures('PASSWORD_PAGE')
|
||||||
|
await this.humanDelay(500, 1200)
|
||||||
|
|
||||||
// Wait for password page and fill it
|
// Wait for password page and fill it
|
||||||
const password = await this.fillPassword()
|
const password = await this.fillPassword()
|
||||||
if (!password) {
|
if (!password) {
|
||||||
@@ -653,6 +670,10 @@ export class AccountCreator {
|
|||||||
const finalEmail = await this.extractEmail()
|
const finalEmail = await this.extractEmail()
|
||||||
const confirmedEmail = finalEmail || emailResult
|
const confirmedEmail = finalEmail || emailResult
|
||||||
|
|
||||||
|
// CRITICAL: Simulate human inspecting birthdate fields
|
||||||
|
await this.human.microGestures('BIRTHDATE_PAGE')
|
||||||
|
await this.humanDelay(400, 1000)
|
||||||
|
|
||||||
// Fill birthdate
|
// Fill birthdate
|
||||||
const birthdate = await this.fillBirthdate()
|
const birthdate = await this.fillBirthdate()
|
||||||
if (!birthdate) {
|
if (!birthdate) {
|
||||||
@@ -667,6 +688,10 @@ export class AccountCreator {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CRITICAL: Simulate human inspecting name fields
|
||||||
|
await this.human.microGestures('NAMES_PAGE')
|
||||||
|
await this.humanDelay(400, 1000)
|
||||||
|
|
||||||
// Fill name fields
|
// Fill name fields
|
||||||
const names = await this.fillNames(confirmedEmail)
|
const names = await this.fillNames(confirmedEmail)
|
||||||
if (!names) {
|
if (!names) {
|
||||||
@@ -942,10 +967,8 @@ export class AccountCreator {
|
|||||||
// Microsoft separates username from domain for outlook.com/hotmail.com addresses
|
// Microsoft separates username from domain for outlook.com/hotmail.com addresses
|
||||||
const emailFillSuccess = await this.retryOperation(
|
const emailFillSuccess = await this.retryOperation(
|
||||||
async () => {
|
async () => {
|
||||||
await emailInput.clear()
|
// CRITICAL FIX: Use humanType() instead of .fill() to avoid detection
|
||||||
await this.humanDelay(800, 1500)
|
await this.human.humanType(emailInput, email, 'EMAIL_INPUT')
|
||||||
await emailInput.fill(email)
|
|
||||||
await this.humanDelay(1200, 2500)
|
|
||||||
|
|
||||||
// SMART VERIFICATION: Check if Microsoft separated the domain
|
// SMART VERIFICATION: Check if Microsoft separated the domain
|
||||||
const inputValue = await emailInput.inputValue().catch(() => '')
|
const inputValue = await emailInput.inputValue().catch(() => '')
|
||||||
@@ -1080,10 +1103,8 @@ export class AccountCreator {
|
|||||||
// CRITICAL: Retry fill with SMART verification (handles domain separation)
|
// CRITICAL: Retry fill with SMART verification (handles domain separation)
|
||||||
const retryFillSuccess = await this.retryOperation(
|
const retryFillSuccess = await this.retryOperation(
|
||||||
async () => {
|
async () => {
|
||||||
await emailInput.clear()
|
// CRITICAL FIX: Use humanType() instead of .fill() to avoid detection
|
||||||
await this.humanDelay(800, 1500)
|
await this.human.humanType(emailInput, newEmail, 'EMAIL_RETRY')
|
||||||
await emailInput.fill(newEmail)
|
|
||||||
await this.humanDelay(1200, 2500)
|
|
||||||
|
|
||||||
// SMART VERIFICATION: Microsoft may separate domain for managed email providers
|
// SMART VERIFICATION: Microsoft may separate domain for managed email providers
|
||||||
const inputValue = await emailInput.inputValue().catch(() => '')
|
const inputValue = await emailInput.inputValue().catch(() => '')
|
||||||
@@ -1158,10 +1179,8 @@ export class AccountCreator {
|
|||||||
|
|
||||||
const retryFillSuccess = await this.retryOperation(
|
const retryFillSuccess = await this.retryOperation(
|
||||||
async () => {
|
async () => {
|
||||||
await emailInput.clear()
|
// CRITICAL FIX: Use humanType() instead of .fill() to avoid detection
|
||||||
await this.humanDelay(800, 1500)
|
await this.human.humanType(emailInput, newEmail, 'EMAIL_AUTO_RETRY')
|
||||||
await emailInput.fill(newEmail)
|
|
||||||
await this.humanDelay(1200, 2500)
|
|
||||||
|
|
||||||
// SMART VERIFICATION: Microsoft may separate domain for managed email providers
|
// SMART VERIFICATION: Microsoft may separate domain for managed email providers
|
||||||
const inputValue = await emailInput.inputValue().catch(() => '')
|
const inputValue = await emailInput.inputValue().catch(() => '')
|
||||||
@@ -1380,10 +1399,8 @@ export class AccountCreator {
|
|||||||
// CRITICAL: Retry fill with verification
|
// CRITICAL: Retry fill with verification
|
||||||
const passwordFillSuccess = await this.retryOperation(
|
const passwordFillSuccess = await this.retryOperation(
|
||||||
async () => {
|
async () => {
|
||||||
await passwordInput.clear()
|
// CRITICAL FIX: Use humanType() instead of .fill() to avoid detection
|
||||||
await this.humanDelay(800, 1500) // INCREASED from 500-1000
|
await this.human.humanType(passwordInput, password, 'PASSWORD_INPUT')
|
||||||
await passwordInput.fill(password)
|
|
||||||
await this.humanDelay(1200, 2500) // INCREASED from 800-2000
|
|
||||||
|
|
||||||
// Verify value was filled correctly
|
// Verify value was filled correctly
|
||||||
const verified = await this.verifyInputValue('input[type="password"]', password)
|
const verified = await this.verifyInputValue('input[type="password"]', password)
|
||||||
@@ -1461,7 +1478,8 @@ export class AccountCreator {
|
|||||||
// CRITICAL: Retry click if it fails
|
// CRITICAL: Retry click if it fails
|
||||||
const dayClickSuccess = await this.retryOperation(
|
const dayClickSuccess = await this.retryOperation(
|
||||||
async () => {
|
async () => {
|
||||||
await dayButton.click({ force: true })
|
// CRITICAL FIX: Use normal click (no force) to avoid bot detection
|
||||||
|
await dayButton.click({ timeout: 5000 })
|
||||||
await this.humanDelay(1500, 2500) // INCREASED delay
|
await this.humanDelay(1500, 2500) // INCREASED delay
|
||||||
|
|
||||||
// Verify dropdown opened
|
// Verify dropdown opened
|
||||||
@@ -1506,7 +1524,8 @@ export class AccountCreator {
|
|||||||
// CRITICAL: Retry click if it fails
|
// CRITICAL: Retry click if it fails
|
||||||
const monthClickSuccess = await this.retryOperation(
|
const monthClickSuccess = await this.retryOperation(
|
||||||
async () => {
|
async () => {
|
||||||
await monthButton.click({ force: true })
|
// CRITICAL FIX: Use normal click (no force) to avoid bot detection
|
||||||
|
await monthButton.click({ timeout: 5000 })
|
||||||
await this.humanDelay(1500, 2500) // INCREASED delay
|
await this.humanDelay(1500, 2500) // INCREASED delay
|
||||||
|
|
||||||
// Verify dropdown opened
|
// Verify dropdown opened
|
||||||
@@ -1560,10 +1579,8 @@ export class AccountCreator {
|
|||||||
// CRITICAL: Retry fill with verification
|
// CRITICAL: Retry fill with verification
|
||||||
const yearFillSuccess = await this.retryOperation(
|
const yearFillSuccess = await this.retryOperation(
|
||||||
async () => {
|
async () => {
|
||||||
await yearInput.clear()
|
// CRITICAL FIX: Use humanType() instead of .fill() to avoid detection
|
||||||
await this.humanDelay(500, 1000)
|
await this.human.humanType(yearInput, birthdate.year.toString(), 'YEAR_INPUT')
|
||||||
await yearInput.fill(birthdate.year.toString())
|
|
||||||
await this.humanDelay(1000, 2000)
|
|
||||||
|
|
||||||
// Verify value was filled correctly
|
// Verify value was filled correctly
|
||||||
const verified = await this.verifyInputValue(
|
const verified = await this.verifyInputValue(
|
||||||
@@ -1667,10 +1684,8 @@ export class AccountCreator {
|
|||||||
// CRITICAL: Retry fill with verification
|
// CRITICAL: Retry fill with verification
|
||||||
const firstNameFillSuccess = await this.retryOperation(
|
const firstNameFillSuccess = await this.retryOperation(
|
||||||
async () => {
|
async () => {
|
||||||
await firstNameInput.clear()
|
// CRITICAL FIX: Use humanType() instead of .fill() to avoid detection
|
||||||
await this.humanDelay(800, 1500) // INCREASED from 500-1000
|
await this.human.humanType(firstNameInput, names.firstName, 'FIRSTNAME_INPUT')
|
||||||
await firstNameInput.fill(names.firstName)
|
|
||||||
await this.humanDelay(1200, 2500) // INCREASED from 800-2000
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
@@ -1712,10 +1727,8 @@ export class AccountCreator {
|
|||||||
// CRITICAL: Retry fill with verification
|
// CRITICAL: Retry fill with verification
|
||||||
const lastNameFillSuccess = await this.retryOperation(
|
const lastNameFillSuccess = await this.retryOperation(
|
||||||
async () => {
|
async () => {
|
||||||
await lastNameInput.clear()
|
// CRITICAL FIX: Use humanType() instead of .fill() to avoid detection
|
||||||
await this.humanDelay(800, 1500) // INCREASED from 500-1000
|
await this.human.humanType(lastNameInput, names.lastName, 'LASTNAME_INPUT')
|
||||||
await lastNameInput.fill(names.lastName)
|
|
||||||
await this.humanDelay(1200, 2500) // INCREASED from 800-2000
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
@@ -2701,8 +2714,7 @@ ${JSON.stringify(accountData, null, 2)}`
|
|||||||
|
|
||||||
// Fill email input
|
// Fill email input
|
||||||
const emailInput = this.page.locator('#EmailAddress').first()
|
const emailInput = this.page.locator('#EmailAddress').first()
|
||||||
await emailInput.fill(recoveryEmailToUse)
|
await this.human.humanType(emailInput, recoveryEmailToUse, 'RECOVERY_EMAIL')
|
||||||
await this.humanDelay(500, 1000)
|
|
||||||
|
|
||||||
// Click Next
|
// Click Next
|
||||||
const nextButton = this.page.locator('#iNext').first()
|
const nextButton = this.page.locator('#iNext').first()
|
||||||
|
|||||||
248
src/account-creation/HumanBehavior.ts
Normal file
248
src/account-creation/HumanBehavior.ts
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
/**
|
||||||
|
* Human Behavior Simulator for Account Creation
|
||||||
|
*
|
||||||
|
* CRITICAL: Microsoft detects bots by analyzing:
|
||||||
|
* 1. Typing speed (instant .fill() = bot, gradual .type() = human)
|
||||||
|
* 2. Mouse movements (no movement = bot, random moves = human)
|
||||||
|
* 3. Pauses (fixed delays = bot, variable pauses = human)
|
||||||
|
* 4. Click patterns (force clicks = bot, natural clicks = human)
|
||||||
|
*
|
||||||
|
* This module ensures account creation is INDISTINGUISHABLE from manual creation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Page } from 'rebrowser-playwright'
|
||||||
|
import { log } from '../util/notifications/Logger'
|
||||||
|
|
||||||
|
export class HumanBehavior {
|
||||||
|
private page: Page
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
this.page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Human-like delay with natural variance
|
||||||
|
* Unlike fixed delays, humans vary greatly in timing
|
||||||
|
*
|
||||||
|
* @param minMs Minimum delay
|
||||||
|
* @param maxMs Maximum delay
|
||||||
|
* @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
|
||||||
|
|
||||||
|
const delay = (Math.random() * (maxMs - minMs) + minMs) * multiplier
|
||||||
|
|
||||||
|
if (shouldThink && context) {
|
||||||
|
log(false, 'CREATOR', `[${context}] 🤔 Thinking pause (${Math.floor(delay)}ms)`, 'log', 'cyan')
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.page.waitForTimeout(Math.floor(delay))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRITICAL: Type text naturally like a human
|
||||||
|
* NEVER use .fill() - it's instant and detectable
|
||||||
|
*
|
||||||
|
* @param locator Playwright locator (input field)
|
||||||
|
* @param text Text to type
|
||||||
|
* @param context Description for logging
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
|
||||||
|
log(false, 'CREATOR', `[${context}] ⌨️ Typing: "${text.substring(0, 20)}${text.length > 20 ? '...' : ''}"`, 'log', 'cyan')
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
// NATURAL VARIANCE:
|
||||||
|
// - Fast keys: common letters (e, a, t, i, o, n) = 80-150ms
|
||||||
|
// - Slow keys: numbers, symbols, shift combos = 200-400ms
|
||||||
|
// - Occasional typos: 5% chance of longer pause (user correcting)
|
||||||
|
|
||||||
|
let charDelay: number
|
||||||
|
const isFastKey = /[eatino]/i.test(char)
|
||||||
|
const isSlowKey = /[^a-z]/i.test(char) // Numbers, symbols, etc.
|
||||||
|
const hasTyro = Math.random() < 0.05 // 5% typo simulation
|
||||||
|
|
||||||
|
if (hasTyro) {
|
||||||
|
charDelay = Math.random() * 400 + 300 // 300-700ms (correcting typo)
|
||||||
|
} else if (isFastKey) {
|
||||||
|
charDelay = Math.random() * 70 + 80 // 80-150ms
|
||||||
|
} else if (isSlowKey) {
|
||||||
|
charDelay = Math.random() * 200 + 200 // 200-400ms
|
||||||
|
} else {
|
||||||
|
charDelay = Math.random() * 100 + 120 // 120-220ms
|
||||||
|
}
|
||||||
|
|
||||||
|
await locator.type(char, { delay: 0 }) // Type instantly
|
||||||
|
await this.page.waitForTimeout(Math.floor(charDelay))
|
||||||
|
}
|
||||||
|
|
||||||
|
log(false, 'CREATOR', `[${context}] ✅ Typing completed`, 'log', 'green')
|
||||||
|
|
||||||
|
// IMPROVEMENT: Random pause after typing (human reviewing input)
|
||||||
|
await this.humanDelay(500, 1500, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRITICAL: Simulate micro mouse movements and scrolls
|
||||||
|
* Real humans constantly move mouse and scroll while reading/thinking
|
||||||
|
*
|
||||||
|
* @param context Description for logging
|
||||||
|
*/
|
||||||
|
async microGestures(context: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
// 60% chance of mouse movement (humans move mouse A LOT)
|
||||||
|
if (Math.random() < 0.6) {
|
||||||
|
const x = Math.floor(Math.random() * 200) + 50 // Random x: 50-250px
|
||||||
|
const y = Math.floor(Math.random() * 150) + 30 // Random y: 30-180px
|
||||||
|
const steps = Math.floor(Math.random() * 5) + 3 // 3-8 steps (smooth movement)
|
||||||
|
|
||||||
|
await this.page.mouse.move(x, y, { steps }).catch(() => {
|
||||||
|
// Mouse move failed - page may be closed or unavailable
|
||||||
|
})
|
||||||
|
|
||||||
|
// VERBOSE logging disabled - too noisy
|
||||||
|
// log(false, 'CREATOR', `[${context}] 🖱️ Mouse moved to (${x}, ${y})`, 'log', 'gray')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 30% chance of scroll (humans scroll to read content)
|
||||||
|
if (Math.random() < 0.3) {
|
||||||
|
const direction = Math.random() < 0.7 ? 1 : -1 // 70% down, 30% up
|
||||||
|
const distance = Math.floor(Math.random() * 200) + 50 // 50-250px
|
||||||
|
const dy = direction * distance
|
||||||
|
|
||||||
|
await this.page.mouse.wheel(0, dy).catch(() => {
|
||||||
|
// Scroll failed - page may be closed or unavailable
|
||||||
|
})
|
||||||
|
|
||||||
|
// VERBOSE logging disabled - too noisy
|
||||||
|
// log(false, 'CREATOR', `[${context}] 📜 Scrolled ${direction > 0 ? 'down' : 'up'} ${distance}px`, 'log', 'gray')
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Gesture execution failed - not critical for operation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRITICAL: Natural click with human behavior
|
||||||
|
* NEVER use { force: true } - it bypasses visibility checks (bot pattern)
|
||||||
|
*
|
||||||
|
* @param locator Playwright locator (button/link)
|
||||||
|
* @param context Description for logging
|
||||||
|
* @param maxRetries Max click attempts (default: 3)
|
||||||
|
* @returns true if click succeeded, false otherwise
|
||||||
|
*/
|
||||||
|
async humanClick(
|
||||||
|
locator: import('rebrowser-playwright').Locator,
|
||||||
|
context: string,
|
||||||
|
maxRetries: number = 3
|
||||||
|
): Promise<boolean> {
|
||||||
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||||
|
try {
|
||||||
|
// CRITICAL: Move mouse to element first (real humans do this)
|
||||||
|
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
|
||||||
|
|
||||||
|
await this.page.mouse.move(
|
||||||
|
box.x + offsetX,
|
||||||
|
box.y + offsetY,
|
||||||
|
{ steps: Math.floor(Math.random() * 3) + 2 } // 2-5 steps
|
||||||
|
).catch(() => { })
|
||||||
|
|
||||||
|
await this.humanDelay(100, 300, context) // Pause before clicking
|
||||||
|
}
|
||||||
|
|
||||||
|
// NATURAL CLICK: No force (respects visibility/interactability)
|
||||||
|
await locator.click({ force: false, timeout: 5000 })
|
||||||
|
|
||||||
|
log(false, 'CREATOR', `[${context}] ✅ Clicked successfully`, 'log', 'green')
|
||||||
|
await this.humanDelay(300, 800, context) // Pause after clicking
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
const msg = error instanceof Error ? error.message : String(error)
|
||||||
|
log(false, 'CREATOR', `[${context}] ❌ Click failed after ${maxRetries} attempts: ${msg}`, 'error')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRITICAL: Simulate human "reading" the page
|
||||||
|
* Real humans pause to read content before interacting
|
||||||
|
*
|
||||||
|
* @param context Description for logging
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
for (let i = 0; i < scrollCount; i++) {
|
||||||
|
await this.microGestures(context)
|
||||||
|
await this.humanDelay(800, 2000, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final reading pause
|
||||||
|
await this.humanDelay(1500, 3500, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRITICAL: Simulate dropdown interaction (more complex than simple clicks)
|
||||||
|
* Real humans: move mouse → hover → click → wait → select option
|
||||||
|
*
|
||||||
|
* @param buttonLocator Dropdown button locator
|
||||||
|
* @param optionLocator Option to select locator
|
||||||
|
* @param context Description for logging
|
||||||
|
* @returns true if interaction succeeded, false otherwise
|
||||||
|
*/
|
||||||
|
async humanDropdownSelect(
|
||||||
|
buttonLocator: import('rebrowser-playwright').Locator,
|
||||||
|
optionLocator: import('rebrowser-playwright').Locator,
|
||||||
|
context: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
// STEP 1: Click dropdown button (with human behavior)
|
||||||
|
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 3: Move mouse randomly inside dropdown (human reading options)
|
||||||
|
await this.microGestures(context)
|
||||||
|
await this.humanDelay(300, 800, context)
|
||||||
|
|
||||||
|
// STEP 4: Click selected option (with human behavior)
|
||||||
|
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)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user