Added a system for tracking mobile search attempts and updated associated tests

This commit is contained in:
2025-11-03 15:34:34 +01:00
parent 2cc9df5278
commit 105bbd1f8a
6 changed files with 110 additions and 16 deletions

View File

@@ -174,6 +174,12 @@ All while maintaining **natural behavior patterns** to minimize detection risk.
---
## ✅ Tests
- `npm run test`: runs the node:test suite with ts-node to validate critical utilities.
---
## 🆘 Getting Help
- 💬 **[Join our Discord](https://discord.gg/h6Z69ZPPCz)** — Community support and updates

View File

@@ -20,6 +20,7 @@
"pre-build": "npm i && npm run clean && node -e \"process.exit(process.env.SKIP_PLAYWRIGHT_INSTALL?0:1)\" || npx playwright install chromium",
"typecheck": "tsc --noEmit",
"build": "tsc",
"test": "node --test --loader ts-node/esm tests",
"start": "node --enable-source-maps ./dist/index.js",
"ts-start": "node --loader ts-node/esm ./src/index.ts",
"dev": "ts-node ./src/index.ts -dev",
@@ -27,7 +28,7 @@
"start:schedule": "node --enable-source-maps ./dist/scheduler.js",
"lint": "eslint \"src/**/*.{ts,tsx}\"",
"prepare": "npm run build",
"setup": "node ./setup/update/setup.mjs",
"setup": "node ./setup/update/setup.mjs",
"kill-chrome-win": "powershell -Command \"Get-Process | Where-Object { $_.MainModule.FileVersionInfo.FileDescription -eq 'Google Chrome for Testing' } | ForEach-Object { Stop-Process -Id $_.Id -Force }\"",
"create-docker": "docker build -t microsoft-rewards-bot ."
},

View File

@@ -29,6 +29,7 @@ import { Analytics } from './util/Analytics'
import { QueryDiversityEngine } from './util/QueryDiversityEngine'
import JobState from './util/JobState'
import { StartupValidator } from './util/StartupValidator'
import { MobileRetryTracker } from './util/MobileRetryTracker'
// Main bot class
@@ -58,7 +59,6 @@ export class MicrosoftRewardsBot {
private pointsInitial: number = 0
private activeWorkers: number
private mobileRetryAttempts: number
private browserFactory: Browser = new Browser(this)
private accounts: Account[]
private workers: Workers
@@ -103,7 +103,6 @@ export class MicrosoftRewardsBot {
this.workers = new Workers(this)
this.humanizer = new Humanizer(this.utils, this.config.humanization)
this.activeWorkers = this.config.clusters
this.mobileRetryAttempts = 0
if (this.config.queryDiversity?.enabled) {
this.queryEngine = new QueryDiversityEngine({
@@ -1070,7 +1069,10 @@ export class MicrosoftRewardsBot {
}
}
async Mobile(account: Account): Promise<{ initialPoints: number; collectedPoints: number }> {
async Mobile(
account: Account,
retryTracker = new MobileRetryTracker(this.config.searchSettings.retryMobileSearchAmount)
): 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()
@@ -1139,6 +1141,9 @@ export class MicrosoftRewardsBot {
}
// 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) {
@@ -1154,21 +1159,21 @@ export class MicrosoftRewardsBot {
const mobileSearchPoints = (await this.browser.func.getSearchPoints()).mobileSearch?.[0]
if (mobileSearchPoints && (mobileSearchPoints.pointProgressMax - mobileSearchPoints.pointProgress) > 0) {
// Increment retry count
this.mobileRetryAttempts++
}
const shouldRetry = retryTracker.registerFailure()
// Exit if retries are exhausted
if (this.mobileRetryAttempts > this.config.searchSettings.retryMobileSearchAmount) {
log(this.isMobile, 'MAIN', `Max retry limit of ${this.config.searchSettings.retryMobileSearchAmount} reached. Exiting retry loop`, 'warn')
} else if (this.mobileRetryAttempts !== 0) {
log(this.isMobile, 'MAIN', `Attempt ${this.mobileRetryAttempts}/${this.config.searchSettings.retryMobileSearchAmount}: Unable to complete mobile searches, bad User-Agent? Increase search delay? Retrying...`, 'log', 'yellow')
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
await this.browser.func.closeBrowser(browser, account.email)
// Close mobile browser before retrying to release resources
await this.browser.func.closeBrowser(browser, account.email)
// Create a new browser and try
return await this.Mobile(account)
// 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')

View File

@@ -0,0 +1,26 @@
export class MobileRetryTracker {
private attempts = 0
private readonly maxRetries: number
constructor(maxRetries: number) {
const normalized = Number.isFinite(maxRetries) ? Math.floor(maxRetries) : 0
this.maxRetries = Math.max(0, normalized)
}
/**
* Register an incomplete mobile search attempt.
* @returns true when another retry should be attempted, false when the retry budget is exhausted.
*/
registerFailure(): boolean {
this.attempts += 1
return this.attempts <= this.maxRetries
}
hasExceeded(): boolean {
return this.attempts > this.maxRetries
}
getAttemptCount(): number {
return this.attempts
}
}

View File

@@ -0,0 +1,28 @@
const test = require('node:test')
const assert = require('node:assert/strict')
const { MobileRetryTracker } = require('../dist/util/MobileRetryTracker.js')
test('MobileRetryTracker stops retries after configured limit', () => {
const tracker = new MobileRetryTracker(2)
assert.equal(tracker.registerFailure(), true)
assert.equal(tracker.hasExceeded(), false)
assert.equal(tracker.getAttemptCount(), 1)
assert.equal(tracker.registerFailure(), true)
assert.equal(tracker.hasExceeded(), false)
assert.equal(tracker.getAttemptCount(), 2)
assert.equal(tracker.registerFailure(), false)
assert.equal(tracker.hasExceeded(), true)
assert.equal(tracker.getAttemptCount(), 3)
})
test('MobileRetryTracker normalizes invalid configuration', () => {
const tracker = new MobileRetryTracker(-3)
assert.equal(tracker.registerFailure(), false)
assert.equal(tracker.hasExceeded(), true)
assert.equal(tracker.getAttemptCount(), 1)
})

View File

@@ -0,0 +1,28 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { MobileRetryTracker } from '../src/util/MobileRetryTracker'
test('MobileRetryTracker stops retries after configured limit', () => {
const tracker = new MobileRetryTracker(2)
assert.equal(tracker.registerFailure(), true)
assert.equal(tracker.hasExceeded(), false)
assert.equal(tracker.getAttemptCount(), 1)
assert.equal(tracker.registerFailure(), true)
assert.equal(tracker.hasExceeded(), false)
assert.equal(tracker.getAttemptCount(), 2)
assert.equal(tracker.registerFailure(), false)
assert.equal(tracker.hasExceeded(), true)
assert.equal(tracker.getAttemptCount(), 3)
})
test('MobileRetryTracker normalizes invalid configuration', () => {
const tracker = new MobileRetryTracker(-3)
assert.equal(tracker.registerFailure(), false)
assert.equal(tracker.hasExceeded(), true)
assert.equal(tracker.getAttemptCount(), 1)
})