Merge pull request #18 from Rempacious/email-fix

Email cutoff fix due to type() and improved internal scheduling
This commit is contained in:
Obsidian
2025-12-09 19:26:41 +01:00
committed by GitHub
5 changed files with 79 additions and 15 deletions

10
package-lock.json generated
View File

@@ -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 @@
}
}
}
}
}

View File

@@ -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' },

View File

@@ -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')

View File

@@ -719,7 +719,10 @@ export class MicrosoftRewardsBot {
return
}
process.exit()
// Don't exit here - let the caller decide (enables scheduler mode)
// For one-time runs, the caller (bootstrap) will exit after run() returns
// For scheduled mode, the scheduler keeps the process alive
return
}
/**
@@ -1214,6 +1217,10 @@ async function main(): Promise<void> {
// One-time execution (scheduling disabled)
await rewardsBot.initialize()
await rewardsBot.run()
// Explicit exit for one-time runs (no scheduler to keep alive)
log('main', 'MAIN', 'One-time run completed. Exiting.', 'log', 'green')
gracefulExit(0)
} catch (e) {
log('main', 'MAIN-ERROR', 'Fatal during run: ' + (e instanceof Error ? e.message : e), 'error')
gracefulExit(1)

View File

@@ -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<void>
@@ -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<void>
*/
private static async typeAppend(locator: Locator, text: string, speed: number = 1.0): Promise<void> {
// 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)
}
/**