mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-09 17:06:15 +00:00
feat: implement HumanTyping module to enhance typing simulation and avoid bot detection
This commit is contained in:
@@ -2633,17 +2633,17 @@ export class AccountCreator {
|
||||
log(false, 'CREATOR', `Normal click failed: ${error1}`, 'log', 'yellow')
|
||||
}
|
||||
|
||||
// Strategy 2: Force click (if cookie banner blocks)
|
||||
// Strategy 2: JavaScript click (avoid force: true - bot detection risk)
|
||||
if (!clickSuccess) {
|
||||
try {
|
||||
log(false, 'CREATOR', '🔄 Retrying with force click...', 'log', 'cyan')
|
||||
await getStartedButton.click({ force: true, timeout: 5000 })
|
||||
log(false, 'CREATOR', '🔄 Retrying with JavaScript click...', 'log', 'cyan')
|
||||
await getStartedButton.evaluate((el: HTMLElement) => el.click())
|
||||
await this.humanDelay(2000, 3000)
|
||||
await this.waitForPageStable('AFTER_GET_STARTED_RETRY', 5000)
|
||||
clickSuccess = true
|
||||
log(false, 'CREATOR', '✅ Clicked "Get started" with force', 'log', 'green')
|
||||
log(false, 'CREATOR', '✅ Clicked "Get started" with JS', 'log', 'green')
|
||||
} catch (error2) {
|
||||
log(false, 'CREATOR', `Force click failed: ${error2}`, 'log', 'yellow')
|
||||
log(false, 'CREATOR', `JS click failed: ${error2}`, 'log', 'yellow')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { Page } from 'playwright'
|
||||
|
||||
import { MicrosoftRewardsBot } from '../index'
|
||||
import { OAuth } from '../interface/OAuth'
|
||||
import { HumanTyping } from '../util/browser/HumanTyping'
|
||||
import { waitForElementSmart, waitForPageReady } from '../util/browser/SmartWait'
|
||||
import { Retry } from '../util/core/Retry'
|
||||
import { logError } from '../util/notifications/Logger'
|
||||
@@ -622,8 +623,8 @@ export class Login {
|
||||
})
|
||||
|
||||
if (!prefilledResult.found) {
|
||||
await page.fill(SELECTORS.emailInput, '')
|
||||
await page.fill(SELECTORS.emailInput, email)
|
||||
// FIXED: Use HumanTyping instead of .fill() to avoid bot detection
|
||||
await HumanTyping.typeEmail(page.locator(SELECTORS.emailInput), email)
|
||||
} else {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Email prefilled')
|
||||
}
|
||||
@@ -684,8 +685,8 @@ export class Login {
|
||||
const blocked = await this.securityDetector.detectSignInBlocked(page)
|
||||
if (blocked) return
|
||||
|
||||
await page.fill(SELECTORS.passwordInput, '')
|
||||
await page.fill(SELECTORS.passwordInput, password)
|
||||
// FIXED: Use HumanTyping instead of .fill() to avoid bot detection
|
||||
await HumanTyping.typePassword(page.locator(SELECTORS.passwordInput), password)
|
||||
|
||||
const submitResult = await waitForElementSmart(page, SELECTORS.submitBtn, {
|
||||
initialTimeoutMs: 500,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Workers } from '../Workers'
|
||||
import { AxiosRequestConfig } from 'axios'
|
||||
import { Counters, DashboardData } from '../../interface/DashboardData'
|
||||
import { GoogleSearch } from '../../interface/Search'
|
||||
import { HumanTyping } from '../../util/browser/HumanTyping'
|
||||
import { waitForElementSmart } from '../../util/browser/SmartWait'
|
||||
|
||||
type GoogleTrendsResponse = [
|
||||
@@ -232,15 +233,18 @@ export class Search extends Workers {
|
||||
|
||||
let navigatedDirectly = false
|
||||
try {
|
||||
// Try focusing and filling instead of clicking (more reliable on mobile)
|
||||
// FIXED: Use HumanTyping instead of .fill() to avoid bot detection
|
||||
await box.focus({ timeout: 2000 }).catch(() => { /* ignore focus errors */ })
|
||||
await box.fill('')
|
||||
await this.bot.utils.wait(200)
|
||||
|
||||
// Clear field using keyboard (natural)
|
||||
await searchPage.keyboard.down(platformControlKey)
|
||||
await searchPage.keyboard.press('A')
|
||||
await searchPage.keyboard.press('Backspace')
|
||||
await searchPage.keyboard.up(platformControlKey)
|
||||
await box.type(query, { delay: 20 })
|
||||
|
||||
// FIXED: Use HumanTyping for natural search query entry
|
||||
await HumanTyping.type(box, query, 1.5) // Fast typing (familiar search action)
|
||||
await searchPage.keyboard.press('Enter')
|
||||
} catch (typeErr) {
|
||||
// As a robust fallback, navigate directly to the search results URL
|
||||
|
||||
@@ -3,6 +3,7 @@ import path from 'path'
|
||||
import type { Page } from 'playwright'
|
||||
|
||||
import { DELAYS } from '../../constants'
|
||||
import { HumanTyping } from '../../util/browser/HumanTyping'
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
import { MorePromotion, PromotionalItem } from '../../interface/DashboardData'
|
||||
@@ -27,9 +28,10 @@ export class SearchOnBing extends Workers {
|
||||
await this.bot.utils.wait(DELAYS.SEARCH_ON_BING_FOCUS)
|
||||
try {
|
||||
await box.focus({ timeout: DELAYS.THIS_OR_THAT_START }).catch(() => { /* ignore */ })
|
||||
await box.fill('')
|
||||
await this.bot.utils.wait(DELAYS.SEARCH_ON_BING_FOCUS)
|
||||
await page.keyboard.type(query, { delay: DELAYS.TYPING_DELAY })
|
||||
|
||||
// FIXED: Use HumanTyping instead of .fill() to avoid bot detection
|
||||
await HumanTyping.type(box, query, 1.5) // Fast typing (familiar search action)
|
||||
await page.keyboard.press('Enter')
|
||||
} catch {
|
||||
const url = `https://www.bing.com/search?q=${encodeURIComponent(query)}`
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Locator, Page } from 'playwright'
|
||||
import readline from 'readline'
|
||||
import { MicrosoftRewardsBot } from '../../index'
|
||||
import { HumanTyping } from '../../util/browser/HumanTyping'
|
||||
import { logError } from '../../util/notifications/Logger'
|
||||
import { generateTOTP } from '../../util/security/Totp'
|
||||
|
||||
@@ -190,8 +191,8 @@ export class TotpHandler {
|
||||
return
|
||||
}
|
||||
|
||||
// Fill code and submit
|
||||
await page.fill('input[name="otc"]', code)
|
||||
// FIXED: Use HumanTyping instead of .fill() to avoid bot detection
|
||||
await HumanTyping.typeTotp(page.locator('input[name="otc"]'), code)
|
||||
await page.keyboard.press('Enter')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', '2FA code submitted')
|
||||
} catch (error) {
|
||||
@@ -248,8 +249,8 @@ export class TotpHandler {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'TOTP input unexpectedly hidden', 'warn')
|
||||
return
|
||||
}
|
||||
await input.fill('')
|
||||
await input.fill(code)
|
||||
// FIXED: Use HumanTyping instead of .fill() to avoid bot detection
|
||||
await HumanTyping.typeTotp(input, code)
|
||||
// Use unified selector system
|
||||
const submit = await this.findFirstVisibleLocator(page, TotpHandler.TOTP_SELECTORS.submit)
|
||||
if (submit) {
|
||||
|
||||
170
src/util/browser/HumanTyping.ts
Normal file
170
src/util/browser/HumanTyping.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* Human-Like Typing Module for Login & Bot Operations
|
||||
*
|
||||
* CRITICAL: Microsoft detects .fill() as instant bot signature
|
||||
* This module provides gradual character-by-character typing with natural variance
|
||||
*
|
||||
* DIFFERENCES from account-creation/HumanBehavior:
|
||||
* - Login typing is FASTER (humans type passwords quickly from muscle memory)
|
||||
* - No typo simulation (users rarely make typos in saved credentials)
|
||||
* - Shorter delays (login is familiar action, not form-filling)
|
||||
*
|
||||
* IMPORTANT: Keep separate from account-creation to avoid coupling
|
||||
*/
|
||||
|
||||
import type { Locator } from 'rebrowser-playwright'
|
||||
|
||||
export class HumanTyping {
|
||||
/**
|
||||
* Type text naturally into field (FAST login typing)
|
||||
*
|
||||
* CRITICAL: Use this instead of .fill() for ALL text inputs
|
||||
*
|
||||
* @param locator Playwright locator (input field)
|
||||
* @param text Text to type
|
||||
* @param speed Typing speed multiplier (1.0 = normal, 0.5 = slow, 2.0 = fast)
|
||||
* @returns Promise<void>
|
||||
*
|
||||
* @example
|
||||
* await HumanTyping.type(page.locator('#email'), 'user@example.com', 1.2) // Fast typing
|
||||
*/
|
||||
static async type(locator: Locator, text: string, speed: number = 1.0): Promise<void> {
|
||||
// SECURITY: Ensure field is visible before typing (avoid bot detection)
|
||||
try {
|
||||
await locator.waitFor({ state: 'visible', timeout: 5000 })
|
||||
} catch {
|
||||
// Field not visible - continue anyway (page may be slow)
|
||||
}
|
||||
|
||||
// IMPROVEMENT: Focus field naturally (humans click before typing)
|
||||
await locator.focus().catch(() => {
|
||||
// Focus failed - not critical
|
||||
})
|
||||
|
||||
// CRITICAL: Clear existing text first (simulate Ctrl+A + Delete)
|
||||
await locator.clear().catch(async () => {
|
||||
// .clear() failed - use keyboard fallback
|
||||
await locator.press('Control+a').catch(() => { })
|
||||
await locator.press('Backspace').catch(() => { })
|
||||
})
|
||||
|
||||
// IMPROVEMENT: Short pause after clearing (human reaction time)
|
||||
await this.delay(50, 150)
|
||||
|
||||
// Type each character with variable timing
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text[i]
|
||||
if (!char) continue // Skip undefined characters
|
||||
|
||||
// SECURITY: Natural typing speed variance (login = fast, familiar action)
|
||||
// Base speed: 40-80ms per character (fast typing)
|
||||
// Speed multiplier: adjustable per context
|
||||
const baseDelay = 40 + Math.random() * 40 // 40-80ms
|
||||
const charDelay = Math.floor(baseDelay / speed)
|
||||
|
||||
// IMPROVEMENT: Slower on special characters (humans need to find keys)
|
||||
const isSpecialChar = /[^a-zA-Z0-9@.]/.test(char)
|
||||
const finalDelay = isSpecialChar ? charDelay * 1.5 : charDelay
|
||||
|
||||
await locator.pressSequentially(char, { delay: finalDelay }).catch(() => {
|
||||
// Typing failed - continue (character may have been typed)
|
||||
})
|
||||
|
||||
// IMPROVEMENT: Occasional micro-pauses (10% chance)
|
||||
if (Math.random() < 0.1 && i > 0) {
|
||||
await this.delay(100, 300)
|
||||
}
|
||||
|
||||
// IMPROVEMENT: Burst typing pattern (humans type groups of characters quickly)
|
||||
// 30% chance to type next 2-3 characters rapidly
|
||||
if (Math.random() < 0.3 && i < text.length - 2) {
|
||||
const burstLength = Math.floor(Math.random() * 2) + 2 // 2-3 chars
|
||||
for (let j = 0; j < burstLength && i + 1 < text.length; j++) {
|
||||
i++
|
||||
const nextChar = text[i]
|
||||
if (nextChar) {
|
||||
await locator.pressSequentially(nextChar, { delay: 10 }).catch(() => { })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IMPROVEMENT: Short pause after typing (human verification)
|
||||
await this.delay(100, 300)
|
||||
}
|
||||
|
||||
/**
|
||||
* Type email address (optimized for email format)
|
||||
*
|
||||
* PATTERN: Humans type emails in 3 parts: [name] @ [domain]
|
||||
*
|
||||
* @param locator Playwright locator (email input)
|
||||
* @param email Email address
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
static async typeEmail(locator: Locator, email: string): Promise<void> {
|
||||
const [localPart, domain] = email.split('@')
|
||||
|
||||
if (!localPart || !domain) {
|
||||
// Invalid email format - fallback to regular typing
|
||||
await this.type(locator, email, 1.2)
|
||||
return
|
||||
}
|
||||
|
||||
// IMPROVEMENT: Type local part (fast)
|
||||
await this.type(locator, localPart, 1.3)
|
||||
|
||||
// IMPROVEMENT: Slight pause before @ (humans verify username)
|
||||
await this.delay(50, 200)
|
||||
|
||||
// Type @ symbol (slightly slower - special key)
|
||||
await locator.pressSequentially('@', { delay: 100 }).catch(() => { })
|
||||
|
||||
// IMPROVEMENT: Slight pause after @ (humans verify domain)
|
||||
await this.delay(50, 150)
|
||||
|
||||
// Type domain (fast)
|
||||
await this.type(locator, domain, 1.4)
|
||||
}
|
||||
|
||||
/**
|
||||
* Type password (FAST - humans type passwords from muscle memory)
|
||||
*
|
||||
* PATTERN: Password typing is FASTEST (no reading, pure muscle memory)
|
||||
*
|
||||
* @param locator Playwright locator (password input)
|
||||
* @param password Password string
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
static async typePassword(locator: Locator, password: string): Promise<void> {
|
||||
// CRITICAL: Passwords typed 2x faster than regular text
|
||||
await this.type(locator, password, 2.0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Type TOTP code (6-digit code from authenticator)
|
||||
*
|
||||
* PATTERN: TOTP typed VERY FAST (user reading from phone, focus)
|
||||
*
|
||||
* @param locator Playwright locator (TOTP input)
|
||||
* @param code 6-digit TOTP code
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
static async typeTotp(locator: Locator, code: string): Promise<void> {
|
||||
// CRITICAL: TOTP codes typed EXTREMELY fast (user focused, limited time)
|
||||
// Speed: 3.0x faster (15-25ms per character)
|
||||
await this.type(locator, code, 3.0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Human-like delay with natural variance
|
||||
*
|
||||
* @param minMs Minimum delay (ms)
|
||||
* @param maxMs Maximum delay (ms)
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
private static async delay(minMs: number, maxMs: number): Promise<void> {
|
||||
const delay = Math.floor(Math.random() * (maxMs - minMs) + minMs)
|
||||
await new Promise(resolve => setTimeout(resolve, delay))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user