mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-10 09:16:16 +00:00
feature: Add scheduling management to the configuration
This commit is contained in:
376
src/index.ts
376
src/index.ts
@@ -865,104 +865,109 @@ export class MicrosoftRewardsBot {
|
||||
async Desktop(account: Account) {
|
||||
log(false,'FLOW','Desktop() invoked')
|
||||
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
|
||||
this.homePage = await browser.newPage()
|
||||
let keepBrowserOpen = false
|
||||
try {
|
||||
this.homePage = await browser.newPage()
|
||||
|
||||
log(this.isMobile, 'MAIN', 'Starting browser')
|
||||
log(this.isMobile, 'MAIN', 'Starting browser')
|
||||
|
||||
// Login into MS Rewards, then optionally stop if compromised
|
||||
await this.login.login(this.homePage, account.email, account.password, account.totp)
|
||||
// Login into MS Rewards, then optionally stop if compromised
|
||||
await this.login.login(this.homePage, account.email, account.password, account.totp)
|
||||
|
||||
if (this.compromisedModeActive) {
|
||||
// User wants the page to remain open for manual recovery. Do not proceed to tasks.
|
||||
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')
|
||||
try {
|
||||
const { ConclusionWebhook } = await import('./util/ConclusionWebhook')
|
||||
await ConclusionWebhook(
|
||||
this.config,
|
||||
'🔐 Security Check',
|
||||
`**Account:** ${account.email}\n**Status:** ${reason}\n**Action:** Browser kept open, activities paused`,
|
||||
undefined,
|
||||
0xFFAA00
|
||||
)
|
||||
} catch {/* ignore */}
|
||||
// Save session for convenience, but do not close the browser
|
||||
try {
|
||||
await saveSessionData(this.config.sessionPath, this.homePage.context(), account.email, this.isMobile)
|
||||
} catch (e) {
|
||||
log(this.isMobile, 'SECURITY', `Failed to save session: ${e instanceof Error ? e.message : String(e)}`, 'warn')
|
||||
if (this.compromisedModeActive) {
|
||||
// User wants the page to remain open for manual recovery. Do not proceed to tasks.
|
||||
keepBrowserOpen = true
|
||||
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')
|
||||
try {
|
||||
const { ConclusionWebhook } = await import('./util/ConclusionWebhook')
|
||||
await ConclusionWebhook(
|
||||
this.config,
|
||||
'🔐 Security Check',
|
||||
`**Account:** ${account.email}\n**Status:** ${reason}\n**Action:** Browser kept open, activities paused`,
|
||||
undefined,
|
||||
0xFFAA00
|
||||
)
|
||||
} catch {/* ignore */}
|
||||
// Save session for convenience, but do not close the browser
|
||||
try {
|
||||
await saveSessionData(this.config.sessionPath, this.homePage.context(), account.email, this.isMobile)
|
||||
} catch (e) {
|
||||
log(this.isMobile, 'SECURITY', `Failed to save session: ${e instanceof Error ? e.message : String(e)}`, 'warn')
|
||||
}
|
||||
return { initialPoints: 0, collectedPoints: 0 }
|
||||
}
|
||||
return { initialPoints: 0, collectedPoints: 0 }
|
||||
}
|
||||
|
||||
await this.browser.func.goHome(this.homePage)
|
||||
await this.browser.func.goHome(this.homePage)
|
||||
|
||||
const data = await this.browser.func.getDashboardData()
|
||||
const data = await this.browser.func.getDashboardData()
|
||||
|
||||
const initial = data.userStatus.availablePoints
|
||||
const initial = data.userStatus.availablePoints
|
||||
|
||||
log(this.isMobile, 'MAIN-POINTS', `Current point count: ${initial}`)
|
||||
log(this.isMobile, 'MAIN-POINTS', `Current point count: ${initial}`)
|
||||
|
||||
const browserEnarablePoints = await this.browser.func.getBrowserEarnablePoints()
|
||||
const browserEnarablePoints = await this.browser.func.getBrowserEarnablePoints()
|
||||
|
||||
// Tally all the desktop points
|
||||
const pointsCanCollect = browserEnarablePoints.dailySetPoints +
|
||||
browserEnarablePoints.desktopSearchPoints +
|
||||
browserEnarablePoints.morePromotionsPoints
|
||||
// Tally all the desktop points
|
||||
const pointsCanCollect = browserEnarablePoints.dailySetPoints +
|
||||
browserEnarablePoints.desktopSearchPoints +
|
||||
browserEnarablePoints.morePromotionsPoints
|
||||
|
||||
log(this.isMobile, 'MAIN-POINTS', `You can earn ${pointsCanCollect} points today`)
|
||||
log(this.isMobile, 'MAIN-POINTS', `You can earn ${pointsCanCollect} points today`)
|
||||
|
||||
if (pointsCanCollect === 0) {
|
||||
// Extra diagnostic breakdown so users know WHY it's zero
|
||||
log(this.isMobile, 'MAIN-POINTS', `Breakdown (desktop): dailySet=${browserEnarablePoints.dailySetPoints} search=${browserEnarablePoints.desktopSearchPoints} promotions=${browserEnarablePoints.morePromotionsPoints}`)
|
||||
log(this.isMobile, 'MAIN-POINTS', 'All desktop earnable buckets are zero. This usually means: tasks already completed today OR the daily reset has not happened yet for your time zone. If you still want to force run activities set execution.runOnZeroPoints=true in config.', 'log', 'yellow')
|
||||
}
|
||||
if (pointsCanCollect === 0) {
|
||||
// Extra diagnostic breakdown so users know WHY it's zero
|
||||
log(this.isMobile, 'MAIN-POINTS', `Breakdown (desktop): dailySet=${browserEnarablePoints.dailySetPoints} search=${browserEnarablePoints.desktopSearchPoints} promotions=${browserEnarablePoints.morePromotionsPoints}`)
|
||||
log(this.isMobile, 'MAIN-POINTS', 'All desktop earnable buckets are zero. This usually means: tasks already completed today OR the daily reset has not happened yet for your time zone. If you still want to force run activities set execution.runOnZeroPoints=true in config.', 'log', 'yellow')
|
||||
}
|
||||
|
||||
// If runOnZeroPoints is false and 0 points to earn, don't continue
|
||||
if (!this.config.runOnZeroPoints && pointsCanCollect === 0) {
|
||||
log(this.isMobile, 'MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!', 'log', 'yellow')
|
||||
// If runOnZeroPoints is false and 0 points to earn, don't continue
|
||||
if (!this.config.runOnZeroPoints && pointsCanCollect === 0) {
|
||||
log(this.isMobile, 'MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!', 'log', 'yellow')
|
||||
return { initialPoints: initial, collectedPoints: 0 }
|
||||
}
|
||||
|
||||
// Close desktop browser
|
||||
await this.browser.func.closeBrowser(browser, account.email)
|
||||
return { initialPoints: initial, collectedPoints: 0 }
|
||||
}
|
||||
// Open a new tab to where the tasks are going to be completed
|
||||
const workerPage = await browser.newPage()
|
||||
|
||||
// Open a new tab to where the tasks are going to be completed
|
||||
const workerPage = await browser.newPage()
|
||||
// Go to homepage on worker page
|
||||
await this.browser.func.goHome(workerPage)
|
||||
|
||||
// Go to homepage on worker page
|
||||
await this.browser.func.goHome(workerPage)
|
||||
// Complete daily set
|
||||
if (this.config.workers.doDailySet) {
|
||||
await this.workers.doDailySet(workerPage, data)
|
||||
}
|
||||
|
||||
// Complete daily set
|
||||
if (this.config.workers.doDailySet) {
|
||||
await this.workers.doDailySet(workerPage, data)
|
||||
}
|
||||
// Complete more promotions
|
||||
if (this.config.workers.doMorePromotions) {
|
||||
await this.workers.doMorePromotions(workerPage, data)
|
||||
}
|
||||
|
||||
// Complete more promotions
|
||||
if (this.config.workers.doMorePromotions) {
|
||||
await this.workers.doMorePromotions(workerPage, data)
|
||||
}
|
||||
// Complete punch cards
|
||||
if (this.config.workers.doPunchCards) {
|
||||
await this.workers.doPunchCard(workerPage, data)
|
||||
}
|
||||
|
||||
// Complete punch cards
|
||||
if (this.config.workers.doPunchCards) {
|
||||
await this.workers.doPunchCard(workerPage, data)
|
||||
}
|
||||
// Do desktop searches
|
||||
if (this.config.workers.doDesktopSearch) {
|
||||
await this.activities.doSearch(workerPage, data)
|
||||
}
|
||||
|
||||
// Do desktop searches
|
||||
if (this.config.workers.doDesktopSearch) {
|
||||
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)
|
||||
const after = await this.browser.func.getCurrentPoints().catch(()=>initial)
|
||||
// Close desktop browser
|
||||
await this.browser.func.closeBrowser(browser, account.email)
|
||||
return {
|
||||
initialPoints: initial,
|
||||
collectedPoints: (after - initial) || 0
|
||||
// Fetch points BEFORE closing (avoid page closed reload error)
|
||||
const after = await this.browser.func.getCurrentPoints().catch(()=>initial)
|
||||
return {
|
||||
initialPoints: initial,
|
||||
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')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -972,121 +977,138 @@ export class MicrosoftRewardsBot {
|
||||
): Promise<{ initialPoints: number; collectedPoints: number }> {
|
||||
log(true,'FLOW','Mobile() invoked')
|
||||
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
|
||||
this.homePage = await browser.newPage()
|
||||
let keepBrowserOpen = false
|
||||
let browserClosed = false
|
||||
try {
|
||||
this.homePage = await browser.newPage()
|
||||
|
||||
log(this.isMobile, 'MAIN', 'Starting browser')
|
||||
log(this.isMobile, 'MAIN', 'Starting browser')
|
||||
|
||||
// Login into MS Rewards, then respect compromised mode
|
||||
await this.login.login(this.homePage, account.email, account.password, account.totp)
|
||||
if (this.compromisedModeActive) {
|
||||
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')
|
||||
try {
|
||||
const { ConclusionWebhook } = await import('./util/ConclusionWebhook')
|
||||
await ConclusionWebhook(
|
||||
this.config,
|
||||
'🔐 Security Check (Mobile)',
|
||||
`**Account:** ${account.email}\n**Status:** ${reason}\n**Action:** Browser kept open, mobile activities paused`,
|
||||
undefined,
|
||||
0xFFAA00
|
||||
)
|
||||
} catch {/* ignore */}
|
||||
try {
|
||||
await saveSessionData(this.config.sessionPath, this.homePage.context(), account.email, this.isMobile)
|
||||
} catch (e) {
|
||||
log(this.isMobile, 'SECURITY', `Failed to save session: ${e instanceof Error ? e.message : String(e)}`, 'warn')
|
||||
// Login into MS Rewards, then respect compromised mode
|
||||
await this.login.login(this.homePage, account.email, account.password, account.totp)
|
||||
if (this.compromisedModeActive) {
|
||||
keepBrowserOpen = true
|
||||
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')
|
||||
try {
|
||||
const { ConclusionWebhook } = await import('./util/ConclusionWebhook')
|
||||
await ConclusionWebhook(
|
||||
this.config,
|
||||
'🔐 Security Check (Mobile)',
|
||||
`**Account:** ${account.email}\n**Status:** ${reason}\n**Action:** Browser kept open, mobile activities paused`,
|
||||
undefined,
|
||||
0xFFAA00
|
||||
)
|
||||
} catch {/* ignore */}
|
||||
try {
|
||||
await saveSessionData(this.config.sessionPath, this.homePage.context(), account.email, this.isMobile)
|
||||
} catch (e) {
|
||||
log(this.isMobile, 'SECURITY', `Failed to save session: ${e instanceof Error ? e.message : String(e)}`, 'warn')
|
||||
}
|
||||
return { initialPoints: 0, collectedPoints: 0 }
|
||||
}
|
||||
return { initialPoints: 0, collectedPoints: 0 }
|
||||
}
|
||||
const accessToken = await this.login.getMobileAccessToken(this.homePage, account.email, account.totp)
|
||||
await this.browser.func.goHome(this.homePage)
|
||||
const accessToken = await this.login.getMobileAccessToken(this.homePage, account.email, account.totp)
|
||||
await this.browser.func.goHome(this.homePage)
|
||||
|
||||
const data = await this.browser.func.getDashboardData()
|
||||
const initialPoints = data.userStatus.availablePoints || 0
|
||||
const data = await this.browser.func.getDashboardData()
|
||||
const initialPoints = data.userStatus.availablePoints || 0
|
||||
|
||||
const browserEnarablePoints = await this.browser.func.getBrowserEarnablePoints()
|
||||
const appEarnablePoints = await this.browser.func.getAppEarnablePoints(accessToken)
|
||||
const browserEnarablePoints = await this.browser.func.getBrowserEarnablePoints()
|
||||
const appEarnablePoints = await this.browser.func.getAppEarnablePoints(accessToken)
|
||||
|
||||
const pointsCanCollect = browserEnarablePoints.mobileSearchPoints + appEarnablePoints.totalEarnablePoints
|
||||
const pointsCanCollect = browserEnarablePoints.mobileSearchPoints + appEarnablePoints.totalEarnablePoints
|
||||
|
||||
log(this.isMobile, 'MAIN-POINTS', `You can earn ${pointsCanCollect} points today (Browser: ${browserEnarablePoints.mobileSearchPoints} points, App: ${appEarnablePoints.totalEarnablePoints} points)`)
|
||||
log(this.isMobile, 'MAIN-POINTS', `You can earn ${pointsCanCollect} points today (Browser: ${browserEnarablePoints.mobileSearchPoints} points, App: ${appEarnablePoints.totalEarnablePoints} points)`)
|
||||
|
||||
if (pointsCanCollect === 0) {
|
||||
log(this.isMobile, 'MAIN-POINTS', `Breakdown (mobile): browserSearch=${browserEnarablePoints.mobileSearchPoints} appTotal=${appEarnablePoints.totalEarnablePoints}`)
|
||||
log(this.isMobile, 'MAIN-POINTS', 'All mobile earnable buckets are zero. Causes: mobile searches already maxed, daily set finished, or daily rollover not reached yet. You can force execution by setting execution.runOnZeroPoints=true.', 'log', 'yellow')
|
||||
}
|
||||
if (pointsCanCollect === 0) {
|
||||
log(this.isMobile, 'MAIN-POINTS', `Breakdown (mobile): browserSearch=${browserEnarablePoints.mobileSearchPoints} appTotal=${appEarnablePoints.totalEarnablePoints}`)
|
||||
log(this.isMobile, 'MAIN-POINTS', 'All mobile earnable buckets are zero. Causes: mobile searches already maxed, daily set finished, or daily rollover not reached yet. You can force execution by setting execution.runOnZeroPoints=true.', 'log', 'yellow')
|
||||
}
|
||||
|
||||
// If runOnZeroPoints is false and 0 points to earn, don't continue
|
||||
if (!this.config.runOnZeroPoints && pointsCanCollect === 0) {
|
||||
log(this.isMobile, 'MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!', 'log', 'yellow')
|
||||
// If runOnZeroPoints is false and 0 points to earn, don't continue
|
||||
if (!this.config.runOnZeroPoints && pointsCanCollect === 0) {
|
||||
log(this.isMobile, 'MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!', 'log', 'yellow')
|
||||
|
||||
return {
|
||||
initialPoints: initialPoints,
|
||||
collectedPoints: 0
|
||||
}
|
||||
}
|
||||
// Do daily check in
|
||||
if (this.config.workers.doDailyCheckIn) {
|
||||
await this.activities.doDailyCheckIn(accessToken, data)
|
||||
}
|
||||
|
||||
// Do read to earn
|
||||
if (this.config.workers.doReadToEarn) {
|
||||
await this.activities.doReadToEarn(accessToken, data)
|
||||
}
|
||||
|
||||
// Do mobile searches
|
||||
const configuredRetries = Number(this.config.searchSettings.retryMobileSearchAmount ?? 0)
|
||||
const maxMobileRetries = Number.isFinite(configuredRetries) ? configuredRetries : 0
|
||||
|
||||
if (this.config.workers.doMobileSearch) {
|
||||
// If no mobile searches data found, stop (Does not always exist on new accounts)
|
||||
if (data.userStatus.counters.mobileSearch) {
|
||||
// Open a new tab to where the tasks are going to be completed
|
||||
const workerPage = await browser.newPage()
|
||||
|
||||
// Go to homepage on worker page
|
||||
await this.browser.func.goHome(workerPage)
|
||||
|
||||
await this.activities.doSearch(workerPage, data)
|
||||
|
||||
// Fetch current search points
|
||||
const mobileSearchPoints = (await this.browser.func.getSearchPoints()).mobileSearch?.[0]
|
||||
|
||||
if (mobileSearchPoints && (mobileSearchPoints.pointProgressMax - mobileSearchPoints.pointProgress) > 0) {
|
||||
const shouldRetry = retryTracker.registerFailure()
|
||||
|
||||
if (!shouldRetry) {
|
||||
const exhaustedAttempts = retryTracker.getAttemptCount()
|
||||
log(this.isMobile, 'MAIN', `Max retry limit of ${maxMobileRetries} reached after ${exhaustedAttempts} attempt(s). Exiting retry loop`, 'warn')
|
||||
} else {
|
||||
const attempt = retryTracker.getAttemptCount()
|
||||
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
|
||||
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 before retry: ${message}`, 'warn')
|
||||
}
|
||||
|
||||
// Create a new browser and try again with the same tracker
|
||||
return await this.Mobile(account, retryTracker)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log(this.isMobile, 'MAIN', 'Unable to fetch search points, your account is most likely too "new" for this! Try again later!', 'warn')
|
||||
}
|
||||
}
|
||||
|
||||
const afterPointAmount = await this.browser.func.getCurrentPoints()
|
||||
|
||||
log(this.isMobile, 'MAIN-POINTS', `The script collected ${afterPointAmount - initialPoints} points today`)
|
||||
|
||||
// Close mobile browser
|
||||
await this.browser.func.closeBrowser(browser, account.email)
|
||||
return {
|
||||
initialPoints: initialPoints,
|
||||
collectedPoints: 0
|
||||
collectedPoints: (afterPointAmount - initialPoints) || 0
|
||||
}
|
||||
}
|
||||
// Do daily check in
|
||||
if (this.config.workers.doDailyCheckIn) {
|
||||
await this.activities.doDailyCheckIn(accessToken, data)
|
||||
}
|
||||
|
||||
// Do read to earn
|
||||
if (this.config.workers.doReadToEarn) {
|
||||
await this.activities.doReadToEarn(accessToken, data)
|
||||
}
|
||||
|
||||
// Do mobile searches
|
||||
const configuredRetries = Number(this.config.searchSettings.retryMobileSearchAmount ?? 0)
|
||||
const maxMobileRetries = Number.isFinite(configuredRetries) ? configuredRetries : 0
|
||||
|
||||
if (this.config.workers.doMobileSearch) {
|
||||
// If no mobile searches data found, stop (Does not always exist on new accounts)
|
||||
if (data.userStatus.counters.mobileSearch) {
|
||||
// Open a new tab to where the tasks are going to be completed
|
||||
const workerPage = await browser.newPage()
|
||||
|
||||
// Go to homepage on worker page
|
||||
await this.browser.func.goHome(workerPage)
|
||||
|
||||
await this.activities.doSearch(workerPage, data)
|
||||
|
||||
// Fetch current search points
|
||||
const mobileSearchPoints = (await this.browser.func.getSearchPoints()).mobileSearch?.[0]
|
||||
|
||||
if (mobileSearchPoints && (mobileSearchPoints.pointProgressMax - mobileSearchPoints.pointProgress) > 0) {
|
||||
const shouldRetry = retryTracker.registerFailure()
|
||||
|
||||
if (!shouldRetry) {
|
||||
const exhaustedAttempts = retryTracker.getAttemptCount()
|
||||
log(this.isMobile, 'MAIN', `Max retry limit of ${maxMobileRetries} reached after ${exhaustedAttempts} attempt(s). Exiting retry loop`, 'warn')
|
||||
} else {
|
||||
const attempt = retryTracker.getAttemptCount()
|
||||
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
|
||||
await this.browser.func.closeBrowser(browser, account.email)
|
||||
|
||||
// Create a new browser and try again with the same tracker
|
||||
return await this.Mobile(account, retryTracker)
|
||||
}
|
||||
} 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')
|
||||
}
|
||||
} else {
|
||||
log(this.isMobile, 'MAIN', 'Unable to fetch search points, your account is most likely too "new" for this! Try again later!', 'warn')
|
||||
}
|
||||
}
|
||||
|
||||
const afterPointAmount = await this.browser.func.getCurrentPoints()
|
||||
|
||||
log(this.isMobile, 'MAIN-POINTS', `The script collected ${afterPointAmount - initialPoints} points today`)
|
||||
|
||||
// Close mobile browser
|
||||
await this.browser.func.closeBrowser(browser, account.email)
|
||||
return {
|
||||
initialPoints: initialPoints,
|
||||
collectedPoints: (afterPointAmount - initialPoints) || 0
|
||||
}
|
||||
}
|
||||
|
||||
private async sendConclusion(summaries: AccountSummary[]) {
|
||||
|
||||
Reference in New Issue
Block a user