mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-10 17:26:17 +00:00
feature: Add scheduling management to the configuration
This commit is contained in:
@@ -11,6 +11,7 @@ export class AccountCreator {
|
|||||||
private dataGenerator: DataGenerator
|
private dataGenerator: DataGenerator
|
||||||
private referralUrl?: string
|
private referralUrl?: string
|
||||||
private rl: readline.Interface
|
private rl: readline.Interface
|
||||||
|
private rlClosed = false
|
||||||
|
|
||||||
constructor(referralUrl?: string) {
|
constructor(referralUrl?: string) {
|
||||||
this.referralUrl = referralUrl
|
this.referralUrl = referralUrl
|
||||||
@@ -19,6 +20,7 @@ export class AccountCreator {
|
|||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout
|
output: process.stdout
|
||||||
})
|
})
|
||||||
|
this.rlClosed = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Human-like delay helper
|
// Human-like delay helper
|
||||||
@@ -111,7 +113,6 @@ export class AccountCreator {
|
|||||||
|
|
||||||
const maxWaitTime = 60000 // 60 seconds
|
const maxWaitTime = 60000 // 60 seconds
|
||||||
const startTime = Date.now()
|
const startTime = Date.now()
|
||||||
const startUrl = this.page.url()
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// STEP 1: Wait for any "Creating account" messages to appear AND disappear
|
// STEP 1: Wait for any "Creating account" messages to appear AND disappear
|
||||||
@@ -313,21 +314,20 @@ export class AccountCreator {
|
|||||||
|
|
||||||
log(false, 'CREATOR', `✅ Account created successfully: ${confirmedEmail}`, 'log', 'green')
|
log(false, 'CREATOR', `✅ Account created successfully: ${confirmedEmail}`, 'log', 'green')
|
||||||
|
|
||||||
// Cleanup readline interface
|
|
||||||
this.rl.close()
|
|
||||||
|
|
||||||
return createdAccount
|
return createdAccount
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const msg = error instanceof Error ? error.message : String(error)
|
const msg = error instanceof Error ? error.message : String(error)
|
||||||
log(false, 'CREATOR', `Error during account creation: ${msg}`, 'error')
|
log(false, 'CREATOR', `Error during account creation: ${msg}`, 'error')
|
||||||
log(false, 'CREATOR', '⚠️ Browser left open for inspection. Press Ctrl+C to exit.', 'warn', 'yellow')
|
log(false, 'CREATOR', '⚠️ Browser left open for inspection. Press Ctrl+C to exit.', 'warn', 'yellow')
|
||||||
|
|
||||||
// Keep browser open and wait indefinitely
|
|
||||||
await new Promise(() => {}) // Never resolves - keeps process alive
|
|
||||||
|
|
||||||
this.rl.close()
|
|
||||||
return null
|
return null
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (!this.rlClosed) {
|
||||||
|
this.rl.close()
|
||||||
|
this.rlClosed = true
|
||||||
|
}
|
||||||
|
} catch {/* ignore */}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,11 +335,12 @@ export class AccountCreator {
|
|||||||
if (this.referralUrl) {
|
if (this.referralUrl) {
|
||||||
log(false, 'CREATOR', `Navigating to referral URL: ${this.referralUrl}`, 'log', 'cyan')
|
log(false, 'CREATOR', `Navigating to referral URL: ${this.referralUrl}`, 'log', 'cyan')
|
||||||
await this.page.goto(this.referralUrl, { waitUntil: 'networkidle', timeout: 60000 })
|
await this.page.goto(this.referralUrl, { waitUntil: 'networkidle', timeout: 60000 })
|
||||||
await this.humanDelay(1500, 3000)
|
|
||||||
|
await this.waitForPageStable('REFERRAL_PAGE', 20000)
|
||||||
|
await this.humanDelay(2000, 3000)
|
||||||
|
|
||||||
log(false, 'CREATOR', 'Looking for "Join Microsoft Rewards" button...', 'log')
|
log(false, 'CREATOR', 'Looking for "Join Microsoft Rewards" button...', 'log')
|
||||||
|
|
||||||
// Multiple selectors for the join button
|
|
||||||
const joinButtonSelectors = [
|
const joinButtonSelectors = [
|
||||||
'a#start-earning-rewards-link',
|
'a#start-earning-rewards-link',
|
||||||
'a.cta.learn-more-btn',
|
'a.cta.learn-more-btn',
|
||||||
@@ -354,7 +355,8 @@ export class AccountCreator {
|
|||||||
|
|
||||||
if (visible) {
|
if (visible) {
|
||||||
await button.click()
|
await button.click()
|
||||||
await this.humanDelay(2000, 4000)
|
await this.humanDelay(2000, 3000)
|
||||||
|
await this.waitForPageStable('AFTER_JOIN_CLICK', 15000)
|
||||||
log(false, 'CREATOR', `✅ Clicked join button with selector: ${selector}`, 'log', 'green')
|
log(false, 'CREATOR', `✅ Clicked join button with selector: ${selector}`, 'log', 'green')
|
||||||
clicked = true
|
clicked = true
|
||||||
break
|
break
|
||||||
@@ -368,14 +370,17 @@ export class AccountCreator {
|
|||||||
const url = 'https://login.live.com/'
|
const url = 'https://login.live.com/'
|
||||||
log(false, 'CREATOR', `No referral URL - navigating to: ${url}`, 'log', 'cyan')
|
log(false, 'CREATOR', `No referral URL - navigating to: ${url}`, 'log', 'cyan')
|
||||||
await this.page.goto(url, { waitUntil: 'networkidle', timeout: 60000 })
|
await this.page.goto(url, { waitUntil: 'networkidle', timeout: 60000 })
|
||||||
await this.humanDelay(1500, 3000)
|
|
||||||
|
await this.waitForPageStable('LOGIN_PAGE', 20000)
|
||||||
|
await this.humanDelay(2000, 3000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async clickCreateAccount(): Promise<void> {
|
private async clickCreateAccount(): Promise<void> {
|
||||||
log(false, 'CREATOR', 'Looking for "Create account" button...', 'log')
|
log(false, 'CREATOR', 'Looking for "Create account" button...', 'log')
|
||||||
|
|
||||||
// Multiple selectors for create account button
|
await this.waitForPageStable('BEFORE_CREATE_ACCOUNT', 15000)
|
||||||
|
|
||||||
const createAccountSelectors = [
|
const createAccountSelectors = [
|
||||||
'a[id*="signup"]',
|
'a[id*="signup"]',
|
||||||
'a[href*="signup"]',
|
'a[href*="signup"]',
|
||||||
@@ -390,7 +395,8 @@ export class AccountCreator {
|
|||||||
try {
|
try {
|
||||||
await button.waitFor({ timeout: 5000 })
|
await button.waitFor({ timeout: 5000 })
|
||||||
await button.click()
|
await button.click()
|
||||||
await this.humanDelay(2000, 3500)
|
await this.humanDelay(2000, 3000)
|
||||||
|
await this.waitForPageStable('AFTER_CREATE_ACCOUNT', 15000)
|
||||||
|
|
||||||
log(false, 'CREATOR', `✅ Clicked "Create account" with selector: ${selector}`, 'log', 'green')
|
log(false, 'CREATOR', `✅ Clicked "Create account" with selector: ${selector}`, 'log', 'green')
|
||||||
return
|
return
|
||||||
@@ -405,6 +411,8 @@ export class AccountCreator {
|
|||||||
private async generateAndFillEmail(): Promise<string | null> {
|
private async generateAndFillEmail(): Promise<string | null> {
|
||||||
log(false, 'CREATOR', '\n=== Email Configuration ===', 'log', 'cyan')
|
log(false, 'CREATOR', '\n=== Email Configuration ===', 'log', 'cyan')
|
||||||
|
|
||||||
|
await this.waitForPageStable('EMAIL_PAGE', 15000)
|
||||||
|
|
||||||
const useAutoGenerate = await this.askQuestion('Generate email automatically? (Y/n): ')
|
const useAutoGenerate = await this.askQuestion('Generate email automatically? (Y/n): ')
|
||||||
|
|
||||||
let email: string
|
let email: string
|
||||||
@@ -429,8 +437,8 @@ export class AccountCreator {
|
|||||||
await nextBtn.waitFor({ timeout: 15000 })
|
await nextBtn.waitFor({ timeout: 15000 })
|
||||||
await nextBtn.click()
|
await nextBtn.click()
|
||||||
await this.humanDelay(2000, 3000)
|
await this.humanDelay(2000, 3000)
|
||||||
|
await this.waitForPageStable('AFTER_EMAIL_SUBMIT', 20000)
|
||||||
|
|
||||||
// Check for any error after clicking Next
|
|
||||||
const result = await this.handleEmailErrors(email)
|
const result = await this.handleEmailErrors(email)
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
return null
|
return null
|
||||||
@@ -440,7 +448,6 @@ export class AccountCreator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async handleEmailErrors(originalEmail: string): Promise<{ success: boolean; email: string | null }> {
|
private async handleEmailErrors(originalEmail: string): Promise<{ success: boolean; email: string | null }> {
|
||||||
// Wait for page to settle
|
|
||||||
await this.humanDelay(1000, 1500)
|
await this.humanDelay(1000, 1500)
|
||||||
|
|
||||||
const errorLocator = this.page.locator('div[id*="Error"], div[role="alert"]').first()
|
const errorLocator = this.page.locator('div[id*="Error"], div[role="alert"]').first()
|
||||||
@@ -488,8 +495,8 @@ export class AccountCreator {
|
|||||||
const nextBtn = this.page.locator('button[data-testid="primaryButton"], button[type="submit"]').first()
|
const nextBtn = this.page.locator('button[data-testid="primaryButton"], button[type="submit"]').first()
|
||||||
await nextBtn.click()
|
await nextBtn.click()
|
||||||
await this.humanDelay(2000, 3000)
|
await this.humanDelay(2000, 3000)
|
||||||
|
await this.waitForPageStable('RETRY_EMAIL', 15000)
|
||||||
|
|
||||||
// Re-check for errors with the new email
|
|
||||||
return await this.handleEmailErrors(newEmail)
|
return await this.handleEmailErrors(newEmail)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,6 +504,7 @@ export class AccountCreator {
|
|||||||
log(false, 'CREATOR', 'Email taken, looking for Microsoft suggestions...', 'log', 'yellow')
|
log(false, 'CREATOR', 'Email taken, looking for Microsoft suggestions...', 'log', 'yellow')
|
||||||
|
|
||||||
await this.humanDelay(2000, 3000)
|
await this.humanDelay(2000, 3000)
|
||||||
|
await this.waitForPageStable('EMAIL_SUGGESTIONS', 10000)
|
||||||
|
|
||||||
// Multiple selectors for suggestions container
|
// Multiple selectors for suggestions container
|
||||||
const suggestionSelectors = [
|
const suggestionSelectors = [
|
||||||
@@ -668,18 +676,16 @@ export class AccountCreator {
|
|||||||
private async fillPassword(): Promise<string | null> {
|
private async fillPassword(): Promise<string | null> {
|
||||||
log(false, 'CREATOR', 'Waiting for password page...', 'log')
|
log(false, 'CREATOR', 'Waiting for password page...', 'log')
|
||||||
|
|
||||||
// Wait for password title to appear (language-independent)
|
|
||||||
await this.page.locator('h1[data-testid="title"]').first().waitFor({ timeout: 20000 })
|
await this.page.locator('h1[data-testid="title"]').first().waitFor({ timeout: 20000 })
|
||||||
|
await this.waitForPageStable('PASSWORD_PAGE', 15000)
|
||||||
await this.humanDelay(1000, 2000)
|
await this.humanDelay(1000, 2000)
|
||||||
|
|
||||||
log(false, 'CREATOR', 'Generating strong password...', 'log')
|
log(false, 'CREATOR', 'Generating strong password...', 'log')
|
||||||
const password = this.dataGenerator.generatePassword()
|
const password = this.dataGenerator.generatePassword()
|
||||||
|
|
||||||
// Find password input
|
|
||||||
const passwordInput = this.page.locator('input[type="password"]').first()
|
const passwordInput = this.page.locator('input[type="password"]').first()
|
||||||
await passwordInput.waitFor({ timeout: 15000 })
|
await passwordInput.waitFor({ timeout: 15000 })
|
||||||
|
|
||||||
// Clear and fill
|
|
||||||
await passwordInput.clear()
|
await passwordInput.clear()
|
||||||
await this.humanDelay(500, 1000)
|
await this.humanDelay(500, 1000)
|
||||||
await passwordInput.fill(password)
|
await passwordInput.fill(password)
|
||||||
@@ -727,11 +733,12 @@ export class AccountCreator {
|
|||||||
private async fillBirthdate(): Promise<{ day: number; month: number; year: number } | null> {
|
private async fillBirthdate(): Promise<{ day: number; month: number; year: number } | null> {
|
||||||
log(false, 'CREATOR', 'Filling birthdate...', 'log')
|
log(false, 'CREATOR', 'Filling birthdate...', 'log')
|
||||||
|
|
||||||
|
await this.waitForPageStable('BIRTHDATE_PAGE', 15000)
|
||||||
|
|
||||||
const birthdate = this.dataGenerator.generateBirthdate()
|
const birthdate = this.dataGenerator.generateBirthdate()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fill day dropdown - wait for the page to be ready
|
await this.humanDelay(2000, 3000)
|
||||||
await this.humanDelay(1000, 1500)
|
|
||||||
|
|
||||||
const dayButton = this.page.locator('button[name="BirthDay"], button#BirthDayDropdown').first()
|
const dayButton = this.page.locator('button[name="BirthDay"], button#BirthDayDropdown').first()
|
||||||
await dayButton.waitFor({ timeout: 15000, state: 'visible' })
|
await dayButton.waitFor({ timeout: 15000, state: 'visible' })
|
||||||
@@ -842,10 +849,13 @@ export class AccountCreator {
|
|||||||
private async fillNames(email: string): Promise<{ firstName: string; lastName: string } | null> {
|
private async fillNames(email: string): Promise<{ firstName: string; lastName: string } | null> {
|
||||||
log(false, 'CREATOR', 'Filling first and last name...', 'log')
|
log(false, 'CREATOR', 'Filling first and last name...', 'log')
|
||||||
|
|
||||||
|
await this.waitForPageStable('NAMES_PAGE', 15000)
|
||||||
|
|
||||||
const names = this.dataGenerator.generateNames(email)
|
const names = this.dataGenerator.generateNames(email)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fill first name with multiple selector fallbacks
|
await this.humanDelay(1000, 2000)
|
||||||
|
|
||||||
const firstNameSelectors = [
|
const firstNameSelectors = [
|
||||||
'input[id*="firstName"]',
|
'input[id*="firstName"]',
|
||||||
'input[name*="firstName"]',
|
'input[name*="firstName"]',
|
||||||
@@ -1044,6 +1054,7 @@ export class AccountCreator {
|
|||||||
if (yesVisible) {
|
if (yesVisible) {
|
||||||
await yesButton.click()
|
await yesButton.click()
|
||||||
await this.humanDelay(2000, 3000)
|
await this.humanDelay(2000, 3000)
|
||||||
|
await this.waitForPageStable('AFTER_KMSI', 15000)
|
||||||
log(false, 'CREATOR', '✅ Accepted "Stay signed in"', 'log', 'green')
|
log(false, 'CREATOR', '✅ Accepted "Stay signed in"', 'log', 'green')
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
@@ -1273,12 +1284,6 @@ export class AccountCreator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
const msg = error instanceof Error ? error.message : String(error)
|
|
||||||
log(false, 'CREATOR', `Warning: Could not verify account: ${msg}`, 'warn', 'yellow')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async handleRewardsWelcomeTour(): Promise<void> {
|
private async handleRewardsWelcomeTour(): Promise<void> {
|
||||||
log(false, 'CREATOR', 'Checking for Microsoft Rewards welcome tour...', 'log', 'cyan')
|
log(false, 'CREATOR', 'Checking for Microsoft Rewards welcome tour...', 'log', 'cyan')
|
||||||
|
|
||||||
@@ -1337,9 +1342,8 @@ export class AccountCreator {
|
|||||||
if (visible) {
|
if (visible) {
|
||||||
log(false, 'CREATOR', `Clicking Next button: ${selector}`, 'log', 'cyan')
|
log(false, 'CREATOR', `Clicking Next button: ${selector}`, 'log', 'cyan')
|
||||||
await button.click()
|
await button.click()
|
||||||
|
|
||||||
// CRITICAL: Wait longer after clicking to let animation complete
|
|
||||||
await this.humanDelay(3000, 4000)
|
await this.humanDelay(3000, 4000)
|
||||||
|
await this.waitForPageStable('AFTER_TOUR_NEXT', 15000)
|
||||||
|
|
||||||
clickedNext = true
|
clickedNext = true
|
||||||
log(false, 'CREATOR', `✅ Clicked Next (step ${i + 1})`, 'log', 'green')
|
log(false, 'CREATOR', `✅ Clicked Next (step ${i + 1})`, 'log', 'green')
|
||||||
@@ -1348,7 +1352,6 @@ export class AccountCreator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!clickedNext) {
|
if (!clickedNext) {
|
||||||
// Try "Pin and start earning" button (final step)
|
|
||||||
const pinButtonSelectors = [
|
const pinButtonSelectors = [
|
||||||
'a#claim-button',
|
'a#claim-button',
|
||||||
'a:has-text("Pin and start earning")',
|
'a:has-text("Pin and start earning")',
|
||||||
@@ -1364,6 +1367,7 @@ export class AccountCreator {
|
|||||||
log(false, 'CREATOR', 'Clicking "Pin and start earning" button', 'log', 'cyan')
|
log(false, 'CREATOR', 'Clicking "Pin and start earning" button', 'log', 'cyan')
|
||||||
await button.click()
|
await button.click()
|
||||||
await this.humanDelay(3000, 4000)
|
await this.humanDelay(3000, 4000)
|
||||||
|
await this.waitForPageStable('AFTER_PIN', 15000)
|
||||||
log(false, 'CREATOR', '✅ Clicked Pin button', 'log', 'green')
|
log(false, 'CREATOR', '✅ Clicked Pin button', 'log', 'green')
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -1430,13 +1434,13 @@ export class AccountCreator {
|
|||||||
log(false, 'CREATOR', 'Clicking "Get started" button', 'log', 'cyan')
|
log(false, 'CREATOR', 'Clicking "Get started" button', 'log', 'cyan')
|
||||||
await button.click()
|
await button.click()
|
||||||
await this.humanDelay(3000, 4000)
|
await this.humanDelay(3000, 4000)
|
||||||
|
await this.waitForPageStable('AFTER_GET_STARTED', 15000)
|
||||||
log(false, 'CREATOR', '✅ Clicked Get started', 'log', 'green')
|
log(false, 'CREATOR', '✅ Clicked Get started', 'log', 'green')
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle any other generic popups
|
|
||||||
const genericCloseSelectors = [
|
const genericCloseSelectors = [
|
||||||
'button[aria-label*="Close"]',
|
'button[aria-label*="Close"]',
|
||||||
'button[aria-label*="Fermer"]',
|
'button[aria-label*="Fermer"]',
|
||||||
@@ -1452,6 +1456,7 @@ export class AccountCreator {
|
|||||||
log(false, 'CREATOR', `Closing popup with selector: ${selector}`, 'log', 'cyan')
|
log(false, 'CREATOR', `Closing popup with selector: ${selector}`, 'log', 'cyan')
|
||||||
await button.click()
|
await button.click()
|
||||||
await this.humanDelay(2000, 3000)
|
await this.humanDelay(2000, 3000)
|
||||||
|
await this.waitForPageStable('AFTER_CLOSE_POPUP', 10000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1500,6 +1505,7 @@ export class AccountCreator {
|
|||||||
log(false, 'CREATOR', `Clicking "Join Microsoft Rewards" button: ${selector}`, 'log', 'cyan')
|
log(false, 'CREATOR', `Clicking "Join Microsoft Rewards" button: ${selector}`, 'log', 'cyan')
|
||||||
await button.click()
|
await button.click()
|
||||||
await this.humanDelay(3000, 5000)
|
await this.humanDelay(3000, 5000)
|
||||||
|
await this.waitForPageStable('AFTER_JOIN', 20000)
|
||||||
log(false, 'CREATOR', '✅ Clicked Join button', 'log', 'green')
|
log(false, 'CREATOR', '✅ Clicked Join button', 'log', 'green')
|
||||||
joined = true
|
joined = true
|
||||||
break
|
break
|
||||||
@@ -1607,7 +1613,10 @@ ${JSON.stringify(accountData, null, 2)}`
|
|||||||
}
|
}
|
||||||
|
|
||||||
async close(): Promise<void> {
|
async close(): Promise<void> {
|
||||||
|
if (!this.rlClosed) {
|
||||||
this.rl.close()
|
this.rl.close()
|
||||||
|
this.rlClosed = true
|
||||||
|
}
|
||||||
if (this.page && !this.page.isClosed()) {
|
if (this.page && !this.page.isClosed()) {
|
||||||
await this.page.close()
|
await this.page.close()
|
||||||
}
|
}
|
||||||
|
|||||||
46
src/index.ts
46
src/index.ts
@@ -865,6 +865,8 @@ export class MicrosoftRewardsBot {
|
|||||||
async Desktop(account: Account) {
|
async Desktop(account: Account) {
|
||||||
log(false,'FLOW','Desktop() invoked')
|
log(false,'FLOW','Desktop() invoked')
|
||||||
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
|
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
|
||||||
|
let keepBrowserOpen = false
|
||||||
|
try {
|
||||||
this.homePage = await browser.newPage()
|
this.homePage = await browser.newPage()
|
||||||
|
|
||||||
log(this.isMobile, 'MAIN', 'Starting browser')
|
log(this.isMobile, 'MAIN', 'Starting browser')
|
||||||
@@ -874,6 +876,7 @@ export class MicrosoftRewardsBot {
|
|||||||
|
|
||||||
if (this.compromisedModeActive) {
|
if (this.compromisedModeActive) {
|
||||||
// User wants the page to remain open for manual recovery. Do not proceed to tasks.
|
// User wants the page to remain open for manual recovery. Do not proceed to tasks.
|
||||||
|
keepBrowserOpen = true
|
||||||
const reason = this.compromisedReason || 'security-issue'
|
const reason = this.compromisedReason || 'security-issue'
|
||||||
log(this.isMobile, 'SECURITY', `Account security check failed (${reason}). Browser kept open for manual review: ${account.email}`, 'warn', 'yellow')
|
log(this.isMobile, 'SECURITY', `Account security check failed (${reason}). Browser kept open for manual review: ${account.email}`, 'warn', 'yellow')
|
||||||
try {
|
try {
|
||||||
@@ -921,9 +924,6 @@ export class MicrosoftRewardsBot {
|
|||||||
// If runOnZeroPoints is false and 0 points to earn, don't continue
|
// If runOnZeroPoints is false and 0 points to earn, don't continue
|
||||||
if (!this.config.runOnZeroPoints && pointsCanCollect === 0) {
|
if (!this.config.runOnZeroPoints && pointsCanCollect === 0) {
|
||||||
log(this.isMobile, 'MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!', 'log', 'yellow')
|
log(this.isMobile, 'MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!', 'log', 'yellow')
|
||||||
|
|
||||||
// Close desktop browser
|
|
||||||
await this.browser.func.closeBrowser(browser, account.email)
|
|
||||||
return { initialPoints: initial, collectedPoints: 0 }
|
return { initialPoints: initial, collectedPoints: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -953,17 +953,22 @@ export class MicrosoftRewardsBot {
|
|||||||
await this.activities.doSearch(workerPage, data)
|
await this.activities.doSearch(workerPage, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save cookies
|
|
||||||
await saveSessionData(this.config.sessionPath, browser, account.email, this.isMobile)
|
|
||||||
|
|
||||||
// Fetch points BEFORE closing (avoid page closed reload error)
|
// Fetch points BEFORE closing (avoid page closed reload error)
|
||||||
const after = await this.browser.func.getCurrentPoints().catch(()=>initial)
|
const after = await this.browser.func.getCurrentPoints().catch(()=>initial)
|
||||||
// Close desktop browser
|
|
||||||
await this.browser.func.closeBrowser(browser, account.email)
|
|
||||||
return {
|
return {
|
||||||
initialPoints: initial,
|
initialPoints: initial,
|
||||||
collectedPoints: (after - initial) || 0
|
collectedPoints: (after - initial) || 0
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
if (!keepBrowserOpen) {
|
||||||
|
try {
|
||||||
|
await this.browser.func.closeBrowser(browser, account.email)
|
||||||
|
} catch (closeError) {
|
||||||
|
const message = closeError instanceof Error ? closeError.message : String(closeError)
|
||||||
|
this.log(this.isMobile, 'CLOSE-BROWSER', `Failed to close desktop context: ${message}`, 'warn')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async Mobile(
|
async Mobile(
|
||||||
@@ -972,6 +977,9 @@ export class MicrosoftRewardsBot {
|
|||||||
): Promise<{ initialPoints: number; collectedPoints: number }> {
|
): Promise<{ initialPoints: number; collectedPoints: number }> {
|
||||||
log(true,'FLOW','Mobile() invoked')
|
log(true,'FLOW','Mobile() invoked')
|
||||||
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
|
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
|
||||||
|
let keepBrowserOpen = false
|
||||||
|
let browserClosed = false
|
||||||
|
try {
|
||||||
this.homePage = await browser.newPage()
|
this.homePage = await browser.newPage()
|
||||||
|
|
||||||
log(this.isMobile, 'MAIN', 'Starting browser')
|
log(this.isMobile, 'MAIN', 'Starting browser')
|
||||||
@@ -979,6 +987,7 @@ export class MicrosoftRewardsBot {
|
|||||||
// Login into MS Rewards, then respect compromised mode
|
// Login into MS Rewards, then respect compromised mode
|
||||||
await this.login.login(this.homePage, account.email, account.password, account.totp)
|
await this.login.login(this.homePage, account.email, account.password, account.totp)
|
||||||
if (this.compromisedModeActive) {
|
if (this.compromisedModeActive) {
|
||||||
|
keepBrowserOpen = true
|
||||||
const reason = this.compromisedReason || 'security-issue'
|
const reason = this.compromisedReason || 'security-issue'
|
||||||
log(this.isMobile, 'SECURITY', `Mobile security check failed (${reason}). Browser kept open for manual review: ${account.email}`, 'warn', 'yellow')
|
log(this.isMobile, 'SECURITY', `Mobile security check failed (${reason}). Browser kept open for manual review: ${account.email}`, 'warn', 'yellow')
|
||||||
try {
|
try {
|
||||||
@@ -1020,8 +1029,6 @@ export class MicrosoftRewardsBot {
|
|||||||
if (!this.config.runOnZeroPoints && pointsCanCollect === 0) {
|
if (!this.config.runOnZeroPoints && pointsCanCollect === 0) {
|
||||||
log(this.isMobile, 'MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!', 'log', 'yellow')
|
log(this.isMobile, 'MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!', 'log', 'yellow')
|
||||||
|
|
||||||
// Close mobile browser
|
|
||||||
await this.browser.func.closeBrowser(browser, account.email)
|
|
||||||
return {
|
return {
|
||||||
initialPoints: initialPoints,
|
initialPoints: initialPoints,
|
||||||
collectedPoints: 0
|
collectedPoints: 0
|
||||||
@@ -1066,7 +1073,13 @@ export class MicrosoftRewardsBot {
|
|||||||
log(this.isMobile, 'MAIN', `Attempt ${attempt}/${maxMobileRetries}: Unable to complete mobile searches, bad User-Agent? Increase search delay? Retrying...`, 'log', 'yellow')
|
log(this.isMobile, 'MAIN', `Attempt ${attempt}/${maxMobileRetries}: Unable to complete mobile searches, bad User-Agent? Increase search delay? Retrying...`, 'log', 'yellow')
|
||||||
|
|
||||||
// Close mobile browser before retrying to release resources
|
// Close mobile browser before retrying to release resources
|
||||||
|
try {
|
||||||
await this.browser.func.closeBrowser(browser, account.email)
|
await this.browser.func.closeBrowser(browser, account.email)
|
||||||
|
browserClosed = true
|
||||||
|
} catch (closeError) {
|
||||||
|
const message = closeError instanceof Error ? closeError.message : String(closeError)
|
||||||
|
this.log(this.isMobile, 'CLOSE-BROWSER', `Failed to close mobile context before retry: ${message}`, 'warn')
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new browser and try again with the same tracker
|
// Create a new browser and try again with the same tracker
|
||||||
return await this.Mobile(account, retryTracker)
|
return await this.Mobile(account, retryTracker)
|
||||||
@@ -1081,12 +1094,21 @@ export class MicrosoftRewardsBot {
|
|||||||
|
|
||||||
log(this.isMobile, 'MAIN-POINTS', `The script collected ${afterPointAmount - initialPoints} points today`)
|
log(this.isMobile, 'MAIN-POINTS', `The script collected ${afterPointAmount - initialPoints} points today`)
|
||||||
|
|
||||||
// Close mobile browser
|
|
||||||
await this.browser.func.closeBrowser(browser, account.email)
|
|
||||||
return {
|
return {
|
||||||
initialPoints: initialPoints,
|
initialPoints: initialPoints,
|
||||||
collectedPoints: (afterPointAmount - initialPoints) || 0
|
collectedPoints: (afterPointAmount - initialPoints) || 0
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
if (!keepBrowserOpen && !browserClosed) {
|
||||||
|
try {
|
||||||
|
await this.browser.func.closeBrowser(browser, account.email)
|
||||||
|
browserClosed = true
|
||||||
|
} catch (closeError) {
|
||||||
|
const message = closeError instanceof Error ? closeError.message : String(closeError)
|
||||||
|
this.log(this.isMobile, 'CLOSE-BROWSER', `Failed to close mobile context: ${message}`, 'warn')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async sendConclusion(summaries: AccountSummary[]) {
|
private async sendConclusion(summaries: AccountSummary[]) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import fs from 'fs'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
import { Account } from '../interface/Account'
|
import { Account } from '../interface/Account'
|
||||||
import { Config, ConfigSaveFingerprint, ConfigBrowser } from '../interface/Config'
|
import { Config, ConfigSaveFingerprint, ConfigBrowser, ConfigScheduling } from '../interface/Config'
|
||||||
import { Util } from './Utils'
|
import { Util } from './Utils'
|
||||||
|
|
||||||
const utils = new Util()
|
const utils = new Util()
|
||||||
@@ -209,6 +209,8 @@ function normalizeConfig(raw: unknown): Config {
|
|||||||
host: typeof dashboardRaw.host === 'string' ? dashboardRaw.host : '127.0.0.1'
|
host: typeof dashboardRaw.host === 'string' ? dashboardRaw.host : '127.0.0.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scheduling = buildSchedulingConfig(n.scheduling)
|
||||||
|
|
||||||
const cfg: Config = {
|
const cfg: Config = {
|
||||||
baseURL: n.baseURL ?? 'https://rewards.bing.com',
|
baseURL: n.baseURL ?? 'https://rewards.bing.com',
|
||||||
sessionPath: n.sessionPath ?? 'sessions',
|
sessionPath: n.sessionPath ?? 'sessions',
|
||||||
@@ -239,12 +241,50 @@ function normalizeConfig(raw: unknown): Config {
|
|||||||
riskManagement,
|
riskManagement,
|
||||||
dryRun,
|
dryRun,
|
||||||
queryDiversity,
|
queryDiversity,
|
||||||
dashboard
|
dashboard,
|
||||||
|
scheduling
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildSchedulingConfig(raw: unknown): ConfigScheduling | undefined {
|
||||||
|
if (!raw || typeof raw !== 'object') return undefined
|
||||||
|
|
||||||
|
const source = raw as Record<string, unknown>
|
||||||
|
const scheduling: ConfigScheduling = {
|
||||||
|
enabled: source.enabled === true,
|
||||||
|
type: typeof source.type === 'string' ? source.type as ConfigScheduling['type'] : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const cronRaw = source.cron
|
||||||
|
if (cronRaw && typeof cronRaw === 'object') {
|
||||||
|
const cronSource = cronRaw as Record<string, unknown>
|
||||||
|
scheduling.cron = {
|
||||||
|
schedule: typeof cronSource.schedule === 'string' ? cronSource.schedule : undefined,
|
||||||
|
workingDirectory: typeof cronSource.workingDirectory === 'string' ? cronSource.workingDirectory : undefined,
|
||||||
|
nodePath: typeof cronSource.nodePath === 'string' ? cronSource.nodePath : undefined,
|
||||||
|
logFile: typeof cronSource.logFile === 'string' ? cronSource.logFile : undefined,
|
||||||
|
user: typeof cronSource.user === 'string' ? cronSource.user : undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskRaw = source.taskScheduler
|
||||||
|
if (taskRaw && typeof taskRaw === 'object') {
|
||||||
|
const taskSource = taskRaw as Record<string, unknown>
|
||||||
|
scheduling.taskScheduler = {
|
||||||
|
taskName: typeof taskSource.taskName === 'string' ? taskSource.taskName : undefined,
|
||||||
|
schedule: typeof taskSource.schedule === 'string' ? taskSource.schedule : undefined,
|
||||||
|
frequency: typeof taskSource.frequency === 'string' ? taskSource.frequency as 'daily' | 'weekly' | 'once' : undefined,
|
||||||
|
workingDirectory: typeof taskSource.workingDirectory === 'string' ? taskSource.workingDirectory : undefined,
|
||||||
|
runAsUser: typeof taskSource.runAsUser === 'boolean' ? taskSource.runAsUser : undefined,
|
||||||
|
highestPrivileges: typeof taskSource.highestPrivileges === 'boolean' ? taskSource.highestPrivileges : undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return scheduling
|
||||||
|
}
|
||||||
|
|
||||||
export function loadAccounts(): Account[] {
|
export function loadAccounts(): Account[] {
|
||||||
try {
|
try {
|
||||||
// 1) CLI dev override
|
// 1) CLI dev override
|
||||||
|
|||||||
Reference in New Issue
Block a user