mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-08 00:26:16 +00:00
feat: Centralize browser instance creation to eliminate duplication in Desktop and Mobile flows
fix: Update error message for HTTP 400 checks to English refactor: Improve logging for network idle wait timeout in Workers fix: Initialize lastError in Axios client to prevent undefined errors chore: Update tsconfig to include path mappings for better module resolution
This commit is contained in:
@@ -25,13 +25,15 @@ async function main(): Promise<void> {
|
||||
}
|
||||
|
||||
// Banner
|
||||
console.log('\n' + '='.repeat(60))
|
||||
log(false, 'CREATOR-CLI', '', 'log') // Empty line
|
||||
log(false, 'CREATOR-CLI', '='.repeat(60), 'log', 'cyan')
|
||||
log(false, 'CREATOR-CLI', '🚀 Microsoft Account Creator', 'log', 'cyan')
|
||||
console.log('='.repeat(60))
|
||||
console.log('\x1b[33m⚠️ DO NOT INTERACT WITH THE BROWSER DURING AUTOMATION\x1b[0m')
|
||||
console.log('\x1b[33m Everything is fully automated. Any interaction may break the process.\x1b[0m')
|
||||
console.log('\x1b[33m Only interact when explicitly asked (e.g., CAPTCHA solving).\x1b[0m')
|
||||
console.log('='.repeat(60) + '\n')
|
||||
log(false, 'CREATOR-CLI', '='.repeat(60), 'log', 'cyan')
|
||||
log(false, 'CREATOR-CLI', '⚠️ DO NOT INTERACT WITH THE BROWSER DURING AUTOMATION', 'warn', 'yellow')
|
||||
log(false, 'CREATOR-CLI', ' Everything is fully automated. Any interaction may break the process.', 'warn', 'yellow')
|
||||
log(false, 'CREATOR-CLI', ' Only interact when explicitly asked (e.g., CAPTCHA solving).', 'warn', 'yellow')
|
||||
log(false, 'CREATOR-CLI', '='.repeat(60), 'log', 'cyan')
|
||||
log(false, 'CREATOR-CLI', '', 'log') // Empty line
|
||||
|
||||
// Display detected arguments
|
||||
if (referralUrl) {
|
||||
@@ -52,7 +54,7 @@ async function main(): Promise<void> {
|
||||
log(false, 'CREATOR-CLI', '💡 Tip: Use -y flag to auto-accept all prompts', 'log', 'gray')
|
||||
}
|
||||
|
||||
console.log()
|
||||
log(false, 'CREATOR-CLI', '', 'log') // Empty line
|
||||
|
||||
// Create a temporary bot instance to access browser creation
|
||||
const bot = new MicrosoftRewardsBot(false)
|
||||
@@ -81,9 +83,10 @@ async function main(): Promise<void> {
|
||||
|
||||
if (result) {
|
||||
// Success banner
|
||||
console.log('\n' + '='.repeat(60))
|
||||
log(false, 'CREATOR-CLI', '', 'log') // Empty line
|
||||
log(false, 'CREATOR-CLI', '='.repeat(60), 'log', 'green')
|
||||
log(false, 'CREATOR-CLI', '✅ ACCOUNT CREATED SUCCESSFULLY!', 'log', 'green')
|
||||
console.log('='.repeat(60))
|
||||
log(false, 'CREATOR-CLI', '='.repeat(60), 'log', 'green')
|
||||
|
||||
// Display account details
|
||||
log(false, 'CREATOR-CLI', `📧 Email: ${result.email}`, 'log', 'cyan')
|
||||
@@ -95,9 +98,10 @@ async function main(): Promise<void> {
|
||||
log(false, 'CREATOR-CLI', '🔗 Referral: Linked', 'log', 'green')
|
||||
}
|
||||
|
||||
console.log('='.repeat(60))
|
||||
log(false, 'CREATOR-CLI', '='.repeat(60), 'log', 'green')
|
||||
log(false, 'CREATOR-CLI', '💾 Account details saved to accounts-created/ directory', 'log', 'green')
|
||||
console.log('='.repeat(60) + '\n')
|
||||
log(false, 'CREATOR-CLI', '='.repeat(60), 'log', 'green')
|
||||
log(false, 'CREATOR-CLI', '', 'log') // Empty line
|
||||
|
||||
// Keep browser open - don't close
|
||||
log(false, 'CREATOR-CLI', '✅ Account creation complete! Browser will remain open.', 'log', 'green')
|
||||
@@ -108,9 +112,11 @@ async function main(): Promise<void> {
|
||||
await new Promise(() => {}) // Never resolves
|
||||
} else {
|
||||
// Failure
|
||||
console.log('\n' + '='.repeat(60))
|
||||
log(false, 'CREATOR-CLI', '', 'log') // Empty line
|
||||
log(false, 'CREATOR-CLI', '='.repeat(60), 'error')
|
||||
log(false, 'CREATOR-CLI', '❌ ACCOUNT CREATION FAILED', 'error')
|
||||
console.log('='.repeat(60) + '\n')
|
||||
log(false, 'CREATOR-CLI', '='.repeat(60), 'error')
|
||||
log(false, 'CREATOR-CLI', '', 'log') // Empty line
|
||||
|
||||
await browserContext.close()
|
||||
process.exit(1)
|
||||
@@ -118,9 +124,11 @@ async function main(): Promise<void> {
|
||||
|
||||
} catch (error) {
|
||||
const msg = error instanceof Error ? error.message : String(error)
|
||||
console.log('\n' + '='.repeat(60))
|
||||
log(false, 'CREATOR-CLI', '', 'log') // Empty line
|
||||
log(false, 'CREATOR-CLI', '='.repeat(60), 'error')
|
||||
log(false, 'CREATOR-CLI', `❌ Fatal error: ${msg}`, 'error')
|
||||
console.log('='.repeat(60) + '\n')
|
||||
log(false, 'CREATOR-CLI', '='.repeat(60), 'error')
|
||||
log(false, 'CREATOR-CLI', '', 'log') // Empty line
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,9 +50,10 @@ export default class BrowserFunc {
|
||||
this.bot.log(this.bot.isMobile, 'GO-HOME', `Account suspension detected by content text (iteration ${iteration})`, 'error')
|
||||
return true
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
// Ignore errors in text check - not critical
|
||||
this.bot.log(this.bot.isMobile, 'GO-HOME', `Suspension text check skipped: ${e}`, 'warn')
|
||||
const errorMsg = error instanceof Error ? error.message : String(error)
|
||||
this.bot.log(this.bot.isMobile, 'GO-HOME', `Suspension text check skipped: ${errorMsg}`, 'warn')
|
||||
}
|
||||
|
||||
return false
|
||||
@@ -141,7 +142,8 @@ export default class BrowserFunc {
|
||||
await target.waitForSelector(SELECTORS.MORE_ACTIVITIES, { timeout: TIMEOUTS.DASHBOARD_WAIT }).catch((error) => {
|
||||
// Continuing is intentional: page may still be functional even if this specific element is missing
|
||||
// The script extraction will catch any real issues
|
||||
this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', `Activities element not found after ${TIMEOUTS.DASHBOARD_WAIT}ms timeout, attempting to proceed: ${error instanceof Error ? error.message : String(error)}`, 'warn')
|
||||
const errorMsg = error instanceof Error ? error.message : String(error)
|
||||
this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', `Activities element not found after ${TIMEOUTS.DASHBOARD_WAIT}ms timeout, attempting to proceed: ${errorMsg}`, 'warn')
|
||||
})
|
||||
|
||||
let scriptContent = await this.extractDashboardScript(target)
|
||||
@@ -151,7 +153,10 @@ export default class BrowserFunc {
|
||||
|
||||
// Force a navigation retry once before failing hard
|
||||
await this.goHome(target)
|
||||
await target.waitForLoadState('domcontentloaded', { timeout: TIMEOUTS.VERY_LONG }).catch(logError('BROWSER-FUNC', 'Dashboard recovery load failed', this.bot.isMobile))
|
||||
await target.waitForLoadState('domcontentloaded', { timeout: TIMEOUTS.VERY_LONG }).catch((error) => {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error)
|
||||
this.bot.log(this.bot.isMobile, 'BROWSER-FUNC', `Dashboard recovery load failed: ${errorMsg}`, 'warn')
|
||||
})
|
||||
await this.bot.utils.wait(this.bot.isMobile ? TIMEOUTS.LONG : TIMEOUTS.MEDIUM)
|
||||
|
||||
scriptContent = await this.extractDashboardScript(target)
|
||||
|
||||
@@ -201,7 +201,7 @@ export default class BrowserUtil {
|
||||
const isNetworkError = $('body.neterror').length
|
||||
const hasHttp400Error = html.includes('HTTP ERROR 400') ||
|
||||
html.includes('This page isn\'t working') ||
|
||||
html.includes('Cette page ne fonctionne pas')
|
||||
html.includes('This page is not working')
|
||||
|
||||
if (isNetworkError || hasHttp400Error) {
|
||||
const errorType = hasHttp400Error ? 'HTTP 400' : 'network error'
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
import type { MicrosoftRewardsBot } from '../index'
|
||||
import type { Account } from '../interface/Account'
|
||||
import { createBrowserInstance } from '../util/BrowserFactory'
|
||||
import { saveSessionData } from '../util/Load'
|
||||
|
||||
export interface DesktopFlowResult {
|
||||
@@ -34,11 +35,8 @@ export class DesktopFlow {
|
||||
async run(account: Account): Promise<DesktopFlowResult> {
|
||||
this.bot.log(false, 'DESKTOP-FLOW', 'Starting desktop automation flow')
|
||||
|
||||
// FIXED: Use proper typed access instead of unsafe type assertion
|
||||
const browserModule = await import('../browser/Browser')
|
||||
const Browser = browserModule.default
|
||||
const browserInstance = new Browser(this.bot)
|
||||
const browser = await browserInstance.createBrowser(account.proxy, account.email)
|
||||
// IMPROVED: Use centralized browser factory to eliminate duplication
|
||||
const browser = await createBrowserInstance(this.bot, account.proxy, account.email)
|
||||
|
||||
let keepBrowserOpen = false
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
import type { MicrosoftRewardsBot } from '../index'
|
||||
import type { Account } from '../interface/Account'
|
||||
import { createBrowserInstance } from '../util/BrowserFactory'
|
||||
import { saveSessionData } from '../util/Load'
|
||||
import { MobileRetryTracker } from '../util/MobileRetryTracker'
|
||||
|
||||
@@ -40,11 +41,8 @@ export class MobileFlow {
|
||||
): Promise<MobileFlowResult> {
|
||||
this.bot.log(true, 'MOBILE-FLOW', 'Starting mobile automation flow')
|
||||
|
||||
// FIXED: Use proper typed access instead of unsafe type assertion
|
||||
const browserModule = await import('../browser/Browser')
|
||||
const Browser = browserModule.default
|
||||
const browserInstance = new Browser(this.bot)
|
||||
const browser = await browserInstance.createBrowser(account.proxy, account.email)
|
||||
// IMPROVED: Use centralized browser factory to eliminate duplication
|
||||
const browser = await createBrowserInstance(this.bot, account.proxy, account.email)
|
||||
|
||||
let keepBrowserOpen = false
|
||||
let browserClosed = false
|
||||
|
||||
@@ -177,7 +177,7 @@ export class Login {
|
||||
const content = await page.content().catch(() => '')
|
||||
const hasHttp400 = content.includes('HTTP ERROR 400') ||
|
||||
content.includes('This page isn\'t working') ||
|
||||
content.includes('Cette page ne fonctionne pas')
|
||||
content.includes('This page is not working')
|
||||
|
||||
if (hasHttp400) {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'HTTP 400 detected in content, reloading...', 'warn')
|
||||
@@ -188,12 +188,20 @@ export class Login {
|
||||
|
||||
await this.disableFido(page)
|
||||
|
||||
const [, , portalCheck] = await Promise.allSettled([
|
||||
const [reloadResult, totpResult, portalCheck] = await Promise.allSettled([
|
||||
this.bot.browser.utils.reloadBadPage(page),
|
||||
this.tryAutoTotp(page, 'initial landing'),
|
||||
page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 3000 })
|
||||
])
|
||||
|
||||
// Log any failures for debugging (non-critical)
|
||||
if (reloadResult.status === 'rejected') {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', `Reload check failed (non-critical): ${reloadResult.reason}`, 'warn')
|
||||
}
|
||||
if (totpResult.status === 'rejected') {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', `Auto-TOTP check failed (non-critical): ${totpResult.reason}`, 'warn')
|
||||
}
|
||||
|
||||
await this.checkAccountLocked(page)
|
||||
|
||||
const alreadyAuthenticated = portalCheck.status === 'fulfilled'
|
||||
@@ -293,10 +301,13 @@ export class Login {
|
||||
|
||||
if (!recoveryUsed) {
|
||||
await this.bot.utils.wait(500)
|
||||
const content = await page.content().catch(() => '')
|
||||
const content = await page.content().catch((err) => {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN-APP', `Failed to get page content for HTTP 400 check: ${err}`, 'warn')
|
||||
return ''
|
||||
})
|
||||
const hasHttp400 = content.includes('HTTP ERROR 400') ||
|
||||
content.includes('This page isn\'t working') ||
|
||||
content.includes('Cette page ne fonctionne pas')
|
||||
content.includes('This page is not working')
|
||||
|
||||
if (hasHttp400) {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN-APP', 'HTTP 400 detected, reloading...', 'warn')
|
||||
@@ -435,7 +446,7 @@ export class Login {
|
||||
const content = await page.content().catch(() => '')
|
||||
const hasHttp400 = content.includes('HTTP ERROR 400') ||
|
||||
content.includes('This page isn\'t working') ||
|
||||
content.includes('Cette page ne fonctionne pas')
|
||||
content.includes('This page is not working')
|
||||
|
||||
if (hasHttp400) {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'HTTP 400 on session check, reloading...', 'warn')
|
||||
@@ -1289,7 +1300,7 @@ export class Login {
|
||||
const content = await page.content().catch(() => '')
|
||||
const hasHttp400 = content.includes('HTTP ERROR 400') ||
|
||||
content.includes('This page isn\'t working') ||
|
||||
content.includes('Cette page ne fonctionne pas')
|
||||
content.includes('This page is not working')
|
||||
|
||||
if (hasHttp400) {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN-BING', 'HTTP 400 detected during Bing verification, reloading...', 'warn')
|
||||
|
||||
@@ -101,7 +101,7 @@ export class Workers {
|
||||
await page.goto(punchCard.parentPromotion.destinationUrl, { referer: this.bot.config.baseURL })
|
||||
|
||||
// Wait for new page to load, max 10 seconds, however try regardless in case of error
|
||||
await page.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => { })
|
||||
await page.waitForLoadState('networkidle', { timeout: 5000 }).catch(logError('PUNCH-CARD', 'Network idle wait timeout (non-critical)', this.bot.isMobile))
|
||||
|
||||
await this.solveActivities(page, activitiesUncompleted, punchCard)
|
||||
|
||||
|
||||
@@ -87,7 +87,8 @@ class AxiosClient {
|
||||
return bypassInstance.request(config)
|
||||
}
|
||||
|
||||
let lastError: unknown
|
||||
// FIXED: Initialize lastError to prevent throwing undefined
|
||||
let lastError: unknown = new Error('Request failed with unknown error')
|
||||
const maxAttempts = 2
|
||||
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
|
||||
33
src/util/BrowserFactory.ts
Normal file
33
src/util/BrowserFactory.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Browser Factory Utility
|
||||
* Eliminates code duplication between Desktop and Mobile flows
|
||||
*
|
||||
* Centralized browser instance creation logic
|
||||
*/
|
||||
|
||||
import type { BrowserContext } from 'rebrowser-playwright'
|
||||
import type { MicrosoftRewardsBot } from '../index'
|
||||
import type { AccountProxy } from '../interface/Account'
|
||||
|
||||
/**
|
||||
* Create a browser instance for the given account
|
||||
* IMPROVEMENT: Extracted from DesktopFlow and MobileFlow to eliminate duplication
|
||||
*
|
||||
* @param bot Bot instance
|
||||
* @param proxy Account proxy configuration
|
||||
* @param email Account email for session naming
|
||||
* @returns Browser context ready to use
|
||||
*
|
||||
* @example
|
||||
* const browser = await createBrowserInstance(bot, account.proxy, account.email)
|
||||
*/
|
||||
export async function createBrowserInstance(
|
||||
bot: MicrosoftRewardsBot,
|
||||
proxy: AccountProxy,
|
||||
email: string
|
||||
): Promise<BrowserContext> {
|
||||
const browserModule = await import('../browser/Browser')
|
||||
const Browser = browserModule.default
|
||||
const browserInstance = new Browser(bot)
|
||||
return await browserInstance.createBrowser(proxy, email)
|
||||
}
|
||||
@@ -28,6 +28,15 @@
|
||||
|
||||
/* Module Resolution Options */
|
||||
"moduleResolution": "node",
|
||||
"baseUrl": "./src",
|
||||
"paths": {
|
||||
"@constants": ["constants"],
|
||||
"@interfaces/*": ["interface/*"],
|
||||
"@utils/*": ["util/*"],
|
||||
"@browser/*": ["browser/*"],
|
||||
"@functions/*": ["functions/*"],
|
||||
"@flows/*": ["flows/*"]
|
||||
},
|
||||
"types": ["node"],
|
||||
"esModuleInterop": true,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user