From e98edd6b930dfcd37edad6b070e113a1ba9be80e Mon Sep 17 00:00:00 2001 From: Rempacious Date: Tue, 9 Dec 2025 14:45:05 +0800 Subject: [PATCH] fix: improve email typing with verification and remove example accounts file --- package-lock.json | 10 +--- src/accounts.example.jsonc | 81 --------------------------------- src/browser/BrowserUtil.ts | 2 +- src/functions/Login.ts | 6 ++- src/util/browser/HumanTyping.ts | 67 +++++++++++++++++++++++++-- 5 files changed, 71 insertions(+), 95 deletions(-) delete mode 100644 src/accounts.example.jsonc diff --git a/package-lock.json b/package-lock.json index aa35799..50da2e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "microsoft-rewards-bot", - "version": "2.60.1", + "version": "3.0.0", "license": "CC-BY-NC-SA-4.0", "dependencies": { "axios": "^1.8.4", @@ -450,7 +450,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz", "integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -738,7 +737,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -989,7 +987,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -1562,7 +1559,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -3217,7 +3213,6 @@ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "playwright-core": "1.52.0" }, @@ -3977,7 +3972,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4291,4 +4285,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/accounts.example.jsonc b/src/accounts.example.jsonc deleted file mode 100644 index 7e76d6b..0000000 --- a/src/accounts.example.jsonc +++ /dev/null @@ -1,81 +0,0 @@ -{ - // See docs/accounts.md for detailed configuration guide - // Maximum 5 accounts recommended - "accounts": [ - { - "enabled": true, - "email": "", - "password": "", - "totp": "", - "recoveryEmail": "", - "phoneNumber": "", - "proxy": { - "proxyAxios": false, - "url": "", - "port": 0, - "username": "", - "password": "" - } - }, - { - "enabled": false, - "email": "", - "password": "", - "totp": "", - "recoveryEmail": "", - "phoneNumber": "", - "proxy": { - "proxyAxios": false, - "url": "", - "port": 0, - "username": "", - "password": "" - } - }, - { - "enabled": false, - "email": "", - "password": "", - "totp": "", - "recoveryEmail": "", - "phoneNumber": "", - "proxy": { - "proxyAxios": false, - "url": "", - "port": 0, - "username": "", - "password": "" - } - }, - { - "enabled": false, - "email": "", - "password": "", - "totp": "", - "recoveryEmail": "", - "phoneNumber": "", - "proxy": { - "proxyAxios": false, - "url": "", - "port": 0, - "username": "", - "password": "" - } - }, - { - "enabled": false, - "email": "", - "password": "", - "totp": "", - "recoveryEmail": "", - "phoneNumber": "", - "proxy": { - "proxyAxios": false, - "url": "", - "port": 0, - "username": "", - "password": "" - } - } - ] -} \ No newline at end of file diff --git a/src/browser/BrowserUtil.ts b/src/browser/BrowserUtil.ts index 17584a4..e8fefb9 100644 --- a/src/browser/BrowserUtil.ts +++ b/src/browser/BrowserUtil.ts @@ -21,7 +21,7 @@ export default class BrowserUtil { { selector: '#idSIButton9', label: 'PrimaryLoginButton' }, { selector: '.ms-Button.ms-Button--primary', label: 'Primary Generic' }, { selector: '.c-glyph.glyph-cancel', label: 'Mobile Welcome Cancel' }, - { selector: '.maybe-later, button[data-automation-id*="maybeLater" i]', label: 'Maybe Later' }, + { selector: '.maybe-later, button[data-automation-id*="maybeLater" i], a.dashboardPopUpPopUpCloseButton', label: 'Maybe Later' }, { selector: '#bnp_btn_reject', label: 'Bing Cookie Reject' }, { selector: '#bnp_btn_accept', label: 'Bing Cookie Accept' }, { selector: '#bnp_close_link', label: 'Bing Cookie Close' }, diff --git a/src/functions/Login.ts b/src/functions/Login.ts index 877770a..ed7babc 100644 --- a/src/functions/Login.ts +++ b/src/functions/Login.ts @@ -322,7 +322,8 @@ export class Login { if (checkCount % 3 === 0) { await Promise.allSettled([ this.passkeyHandler.handlePasskeyPrompts(page, 'oauth'), - this.totpHandler.tryAutoTotp(page, 'mobile-oauth') + this.totpHandler.tryAutoTotp(page, 'mobile-oauth'), + this.bot.browser.utils.tryDismissAllMessages(page) ]) } @@ -837,7 +838,8 @@ export class Login { if (checkCount % 3 === 0) { await Promise.allSettled([ this.passkeyHandler.handlePasskeyPrompts(page, 'main'), - this.totpHandler.tryAutoTotp(page, 'post-password wait') + this.totpHandler.tryAutoTotp(page, 'post-password wait'), + this.bot.browser.utils.tryDismissAllMessages(page) ]) } else { await this.passkeyHandler.handlePasskeyPrompts(page, 'main') diff --git a/src/util/browser/HumanTyping.ts b/src/util/browser/HumanTyping.ts index fbdf84e..2265a4c 100644 --- a/src/util/browser/HumanTyping.ts +++ b/src/util/browser/HumanTyping.ts @@ -98,6 +98,9 @@ export class HumanTyping { * * PATTERN: Humans type emails in 3 parts: [name] @ [domain] * + * CRITICAL FIX: Previous version called type() for domain which cleared the field, + * erasing the localPart. Now we use typeAppend() to preserve existing content. + * * @param locator Playwright locator (email input) * @param email Email address * @returns Promise @@ -111,20 +114,78 @@ export class HumanTyping { return } - // IMPROVEMENT: Type local part (fast) + // IMPROVEMENT: Type local part (fast) - this clears and types 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) + // CRITICAL: Use pressSequentially, NOT type() which would clear the field 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) + // CRITICAL FIX: Use typeAppend() to NOT clear the field (preserve localPart + @) + await this.typeAppend(locator, domain, 1.4) + + // VERIFICATION: Check that input contains the full email + await this.delay(100, 200) + const actualValue = await locator.inputValue().catch(() => '') + if (!actualValue.includes(localPart) || !actualValue.includes('@') || !actualValue.includes(domain)) { + console.warn(`[HumanTyping.typeEmail] WARNING: Email may not have been typed correctly. Expected: ${email}, Got: ${actualValue}`) + } + } + + /** + * Type text without clearing the field first (append to existing content) + * + * CRITICAL: Use this when you want to ADD to existing text, not replace it + * + * @param locator Playwright locator (input field) + * @param text Text to append + * @param speed Typing speed multiplier (1.0 = normal, 0.5 = slow, 2.0 = fast) + * @returns Promise + */ + private static async typeAppend(locator: Locator, text: string, speed: number = 1.0): Promise { + // Type each character with variable timing (no clearing) + for (let i = 0; i < text.length; i++) { + const char = text[i] + if (!char) continue // Skip undefined characters + + // Natural typing speed variance + const baseDelay = 40 + Math.random() * 40 // 40-80ms + const charDelay = Math.floor(baseDelay / speed) + + // Slower on special characters + 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) + }) + + // Occasional micro-pauses (10% chance) + if (Math.random() < 0.1 && i > 0) { + await this.delay(100, 300) + } + + // Burst typing pattern (30% chance) + 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(() => { }) + } + } + } + } + + // Short pause after typing + await this.delay(100, 300) } /**