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 ## 🆘 Getting Help
- 💬 **[Join our Discord](https://discord.gg/h6Z69ZPPCz)** — Community support and updates - 💬 **[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", "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", "typecheck": "tsc --noEmit",
"build": "tsc", "build": "tsc",
"test": "node --test --loader ts-node/esm tests",
"start": "node --enable-source-maps ./dist/index.js", "start": "node --enable-source-maps ./dist/index.js",
"ts-start": "node --loader ts-node/esm ./src/index.ts", "ts-start": "node --loader ts-node/esm ./src/index.ts",
"dev": "ts-node ./src/index.ts -dev", "dev": "ts-node ./src/index.ts -dev",

View File

@@ -29,6 +29,7 @@ import { Analytics } from './util/Analytics'
import { QueryDiversityEngine } from './util/QueryDiversityEngine' import { QueryDiversityEngine } from './util/QueryDiversityEngine'
import JobState from './util/JobState' import JobState from './util/JobState'
import { StartupValidator } from './util/StartupValidator' import { StartupValidator } from './util/StartupValidator'
import { MobileRetryTracker } from './util/MobileRetryTracker'
// Main bot class // Main bot class
@@ -58,7 +59,6 @@ export class MicrosoftRewardsBot {
private pointsInitial: number = 0 private pointsInitial: number = 0
private activeWorkers: number private activeWorkers: number
private mobileRetryAttempts: number
private browserFactory: Browser = new Browser(this) private browserFactory: Browser = new Browser(this)
private accounts: Account[] private accounts: Account[]
private workers: Workers private workers: Workers
@@ -103,7 +103,6 @@ export class MicrosoftRewardsBot {
this.workers = new Workers(this) this.workers = new Workers(this)
this.humanizer = new Humanizer(this.utils, this.config.humanization) this.humanizer = new Humanizer(this.utils, this.config.humanization)
this.activeWorkers = this.config.clusters this.activeWorkers = this.config.clusters
this.mobileRetryAttempts = 0
if (this.config.queryDiversity?.enabled) { if (this.config.queryDiversity?.enabled) {
this.queryEngine = new QueryDiversityEngine({ 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') 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)
this.homePage = await browser.newPage() this.homePage = await browser.newPage()
@@ -1139,6 +1141,9 @@ export class MicrosoftRewardsBot {
} }
// Do mobile searches // Do mobile searches
const configuredRetries = Number(this.config.searchSettings.retryMobileSearchAmount ?? 0)
const maxMobileRetries = Number.isFinite(configuredRetries) ? configuredRetries : 0
if (this.config.workers.doMobileSearch) { if (this.config.workers.doMobileSearch) {
// If no mobile searches data found, stop (Does not always exist on new accounts) // If no mobile searches data found, stop (Does not always exist on new accounts)
if (data.userStatus.counters.mobileSearch) { if (data.userStatus.counters.mobileSearch) {
@@ -1154,21 +1159,21 @@ export class MicrosoftRewardsBot {
const mobileSearchPoints = (await this.browser.func.getSearchPoints()).mobileSearch?.[0] const mobileSearchPoints = (await this.browser.func.getSearchPoints()).mobileSearch?.[0]
if (mobileSearchPoints && (mobileSearchPoints.pointProgressMax - mobileSearchPoints.pointProgress) > 0) { if (mobileSearchPoints && (mobileSearchPoints.pointProgressMax - mobileSearchPoints.pointProgress) > 0) {
// Increment retry count const shouldRetry = retryTracker.registerFailure()
this.mobileRetryAttempts++
}
// Exit if retries are exhausted if (!shouldRetry) {
if (this.mobileRetryAttempts > this.config.searchSettings.retryMobileSearchAmount) { const exhaustedAttempts = retryTracker.getAttemptCount()
log(this.isMobile, 'MAIN', `Max retry limit of ${this.config.searchSettings.retryMobileSearchAmount} reached. Exiting retry loop`, 'warn') log(this.isMobile, 'MAIN', `Max retry limit of ${maxMobileRetries} reached after ${exhaustedAttempts} attempt(s). Exiting retry loop`, 'warn')
} else if (this.mobileRetryAttempts !== 0) { } else {
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') 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 // Close mobile browser before retrying to release resources
await this.browser.func.closeBrowser(browser, account.email) await this.browser.func.closeBrowser(browser, account.email)
// Create a new browser and try // Create a new browser and try again with the same tracker
return await this.Mobile(account) return await this.Mobile(account, retryTracker)
}
} }
} else { } else {
log(this.isMobile, 'MAIN', 'Unable to fetch search points, your account is most likely too "new" for this! Try again later!', 'warn') 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)
})