mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-09 17:06:15 +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 { log } from '../util/notifications/Logger'
|
||||
import { DataGenerator } from './DataGenerator'
|
||||
import { HumanBehavior } from './HumanBehavior'
|
||||
import { CreatedAccount } from './types'
|
||||
|
||||
export class AccountCreator {
|
||||
private page!: Page
|
||||
private human!: HumanBehavior
|
||||
private dataGenerator: DataGenerator
|
||||
private referralUrl?: string
|
||||
private recoveryEmail?: string
|
||||
@@ -27,10 +29,10 @@ export class AccountCreator {
|
||||
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> {
|
||||
const delay = Math.random() * (maxMs - minMs) + minMs
|
||||
await this.page.waitForTimeout(Math.floor(delay))
|
||||
await this.human.humanDelay(minMs, maxMs)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -618,14 +620,25 @@ export class AccountCreator {
|
||||
try {
|
||||
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
|
||||
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
|
||||
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)
|
||||
const emailResult = await this.generateAndFillEmail(this.autoAccept)
|
||||
if (!emailResult) {
|
||||
@@ -635,6 +648,10 @@ export class AccountCreator {
|
||||
|
||||
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
|
||||
const password = await this.fillPassword()
|
||||
if (!password) {
|
||||
@@ -653,6 +670,10 @@ export class AccountCreator {
|
||||
const finalEmail = await this.extractEmail()
|
||||
const confirmedEmail = finalEmail || emailResult
|
||||
|
||||
// CRITICAL: Simulate human inspecting birthdate fields
|
||||
await this.human.microGestures('BIRTHDATE_PAGE')
|
||||
await this.humanDelay(400, 1000)
|
||||
|
||||
// Fill birthdate
|
||||
const birthdate = await this.fillBirthdate()
|
||||
if (!birthdate) {
|
||||
@@ -667,6 +688,10 @@ export class AccountCreator {
|
||||
return null
|
||||
}
|
||||
|
||||
// CRITICAL: Simulate human inspecting name fields
|
||||
await this.human.microGestures('NAMES_PAGE')
|
||||
await this.humanDelay(400, 1000)
|
||||
|
||||
// Fill name fields
|
||||
const names = await this.fillNames(confirmedEmail)
|
||||
if (!names) {
|
||||
@@ -942,10 +967,8 @@ export class AccountCreator {
|
||||
// Microsoft separates username from domain for outlook.com/hotmail.com addresses
|
||||
const emailFillSuccess = await this.retryOperation(
|
||||
async () => {
|
||||
await emailInput.clear()
|
||||
await this.humanDelay(800, 1500)
|
||||
await emailInput.fill(email)
|
||||
await this.humanDelay(1200, 2500)
|
||||
// CRITICAL FIX: Use humanType() instead of .fill() to avoid detection
|
||||
await this.human.humanType(emailInput, email, 'EMAIL_INPUT')
|
||||
|
||||
// SMART VERIFICATION: Check if Microsoft separated the domain
|
||||
const inputValue = await emailInput.inputValue().catch(() => '')
|
||||
@@ -1080,10 +1103,8 @@ export class AccountCreator {
|
||||
// CRITICAL: Retry fill with SMART verification (handles domain separation)
|
||||
const retryFillSuccess = await this.retryOperation(
|
||||
async () => {
|
||||
await emailInput.clear()
|
||||
await this.humanDelay(800, 1500)
|
||||
await emailInput.fill(newEmail)
|
||||
await this.humanDelay(1200, 2500)
|
||||
// CRITICAL FIX: Use humanType() instead of .fill() to avoid detection
|
||||
await this.human.humanType(emailInput, newEmail, 'EMAIL_RETRY')
|
||||
|
||||
// SMART VERIFICATION: Microsoft may separate domain for managed email providers
|
||||
const inputValue = await emailInput.inputValue().catch(() => '')
|
||||
@@ -1158,10 +1179,8 @@ export class AccountCreator {
|
||||
|
||||
const retryFillSuccess = await this.retryOperation(
|
||||
async () => {
|
||||
await emailInput.clear()
|
||||
await this.humanDelay(800, 1500)
|
||||
await emailInput.fill(newEmail)
|
||||
await this.humanDelay(1200, 2500)
|
||||
// CRITICAL FIX: Use humanType() instead of .fill() to avoid detection
|
||||
await this.human.humanType(emailInput, newEmail, 'EMAIL_AUTO_RETRY')
|
||||
|
||||
// SMART VERIFICATION: Microsoft may separate domain for managed email providers
|
||||
const inputValue = await emailInput.inputValue().catch(() => '')
|
||||
@@ -1380,10 +1399,8 @@ export class AccountCreator {
|
||||
// CRITICAL: Retry fill with verification
|
||||
const passwordFillSuccess = await this.retryOperation(
|
||||
async () => {
|
||||
await passwordInput.clear()
|
||||
await this.humanDelay(800, 1500) // INCREASED from 500-1000
|
||||
await passwordInput.fill(password)
|
||||
await this.humanDelay(1200, 2500) // INCREASED from 800-2000
|
||||
// CRITICAL FIX: Use humanType() instead of .fill() to avoid detection
|
||||
await this.human.humanType(passwordInput, password, 'PASSWORD_INPUT')
|
||||
|
||||
// Verify value was filled correctly
|
||||
const verified = await this.verifyInputValue('input[type="password"]', password)
|
||||
@@ -1461,7 +1478,8 @@ export class AccountCreator {
|
||||
// CRITICAL: Retry click if it fails
|
||||
const dayClickSuccess = await this.retryOperation(
|
||||
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
|
||||
|
||||
// Verify dropdown opened
|
||||
@@ -1506,7 +1524,8 @@ export class AccountCreator {
|
||||
// CRITICAL: Retry click if it fails
|
||||
const monthClickSuccess = await this.retryOperation(
|
||||
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
|
||||
|
||||
// Verify dropdown opened
|
||||
@@ -1560,10 +1579,8 @@ export class AccountCreator {
|
||||
// CRITICAL: Retry fill with verification
|
||||
const yearFillSuccess = await this.retryOperation(
|
||||
async () => {
|
||||
await yearInput.clear()
|
||||
await this.humanDelay(500, 1000)
|
||||
await yearInput.fill(birthdate.year.toString())
|
||||
await this.humanDelay(1000, 2000)
|
||||
// CRITICAL FIX: Use humanType() instead of .fill() to avoid detection
|
||||
await this.human.humanType(yearInput, birthdate.year.toString(), 'YEAR_INPUT')
|
||||
|
||||
// Verify value was filled correctly
|
||||
const verified = await this.verifyInputValue(
|
||||
@@ -1667,10 +1684,8 @@ export class AccountCreator {
|
||||
// CRITICAL: Retry fill with verification
|
||||
const firstNameFillSuccess = await this.retryOperation(
|
||||
async () => {
|
||||
await firstNameInput.clear()
|
||||
await this.humanDelay(800, 1500) // INCREASED from 500-1000
|
||||
await firstNameInput.fill(names.firstName)
|
||||
await this.humanDelay(1200, 2500) // INCREASED from 800-2000
|
||||
// CRITICAL FIX: Use humanType() instead of .fill() to avoid detection
|
||||
await this.human.humanType(firstNameInput, names.firstName, 'FIRSTNAME_INPUT')
|
||||
|
||||
return true
|
||||
},
|
||||
@@ -1712,10 +1727,8 @@ export class AccountCreator {
|
||||
// CRITICAL: Retry fill with verification
|
||||
const lastNameFillSuccess = await this.retryOperation(
|
||||
async () => {
|
||||
await lastNameInput.clear()
|
||||
await this.humanDelay(800, 1500) // INCREASED from 500-1000
|
||||
await lastNameInput.fill(names.lastName)
|
||||
await this.humanDelay(1200, 2500) // INCREASED from 800-2000
|
||||
// CRITICAL FIX: Use humanType() instead of .fill() to avoid detection
|
||||
await this.human.humanType(lastNameInput, names.lastName, 'LASTNAME_INPUT')
|
||||
|
||||
return true
|
||||
},
|
||||
@@ -2701,8 +2714,7 @@ ${JSON.stringify(accountData, null, 2)}`
|
||||
|
||||
// Fill email input
|
||||
const emailInput = this.page.locator('#EmailAddress').first()
|
||||
await emailInput.fill(recoveryEmailToUse)
|
||||
await this.humanDelay(500, 1000)
|
||||
await this.human.humanType(emailInput, recoveryEmailToUse, 'RECOVERY_EMAIL')
|
||||
|
||||
// Click Next
|
||||
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