From 2255845dbee989a4625d74a7bb15bdb6ea9ae864 Mon Sep 17 00:00:00 2001 From: LightZirconite Date: Tue, 4 Nov 2025 22:37:43 +0100 Subject: [PATCH] feat: add job state reset functionality and improve user prompts --- src/config.jsonc | 2 +- src/index.ts | 60 +++++++++++++++++++++++++++++++++++++++++--- src/util/JobState.ts | 4 +++ 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/config.jsonc b/src/config.jsonc index d7390ec..8ce9961 100644 --- a/src/config.jsonc +++ b/src/config.jsonc @@ -21,7 +21,7 @@ "parallel": false, "runOnZeroPoints": false, "clusters": 1, - "passesPerRun": 1 + "passesPerRun": 1 // Number of times to run through all accounts (set to 3 to run 3 times even if already completed) }, "jobState": { "enabled": true, diff --git a/src/index.ts b/src/index.ts index 54bb08b..e46033c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import type { Page } from 'playwright' import fs from 'fs' import path from 'path' import { spawn } from 'child_process' +import { createInterface } from 'readline' import Browser from './browser/Browser' import BrowserFunc from './browser/BrowserFunc' @@ -169,6 +170,43 @@ export class MicrosoftRewardsBot { return value === '1' || lower === 'true' || lower === 'yes' } + private async promptResetJobState(): Promise { + // Skip prompt in non-interactive environments (Docker, CI, scheduled tasks) + if (!process.stdin.isTTY) { + log('main','TASK','Non-interactive environment detected - keeping job state', 'warn') + return false + } + + const rl = createInterface({ + input: process.stdin, + output: process.stdout + }) + + return new Promise((resolve) => { + rl.question('\n⚠️ Reset job state and run all accounts again? (y/N): ', (answer) => { + rl.close() + const trimmed = answer.trim().toLowerCase() + resolve(trimmed === 'y' || trimmed === 'yes') + }) + }) + } + + private resetAllJobStates(): void { + if (!this.accountJobState) return + + const jobStateDir = this.accountJobState.getJobStateDir() + if (!fs.existsSync(jobStateDir)) return + + const files = fs.readdirSync(jobStateDir).filter(f => f.endsWith('.json')) + for (const file of files) { + try { + fs.unlinkSync(path.join(jobStateDir, file)) + } catch { + // Ignore errors + } + } + } + async run() { this.printBanner() log('main', 'MAIN', `Bot started with ${this.config.clusters} clusters`) @@ -479,6 +517,22 @@ export class MicrosoftRewardsBot { } private async runTasks(accounts: Account[]) { + // Check if all accounts are already completed and prompt user + const accountDayKey = this.utils.getFormattedDate() + const allCompleted = accounts.every(acc => this.shouldSkipAccount(acc.email, accountDayKey)) + + if (allCompleted && accounts.length > 0) { + log('main','TASK',`All accounts already completed on ${accountDayKey}`, 'warn', 'yellow') + const shouldReset = await this.promptResetJobState() + if (shouldReset) { + this.resetAllJobStates() + log('main','TASK','Job state reset - proceeding with all accounts', 'log', 'green') + } else { + log('main','TASK','Keeping existing job state - exiting', 'log') + return + } + } + for (const account of accounts) { // If a global standby is active due to security/banned, stop processing further accounts if (this.globalStandby.active) { @@ -490,9 +544,9 @@ export class MicrosoftRewardsBot { log('main','TASK',`Stopping remaining accounts due to ban on ${this.bannedTriggered.email}: ${this.bannedTriggered.reason}`,'warn') break } - const accountDayKey = this.utils.getFormattedDate() - if (this.shouldSkipAccount(account.email, accountDayKey)) { - log('main','TASK',`Skipping account ${account.email}: already completed on ${accountDayKey} (job-state resume)`, 'warn') + const currentDayKey = this.utils.getFormattedDate() + if (this.shouldSkipAccount(account.email, currentDayKey)) { + log('main','TASK',`Skipping account ${account.email}: already completed on ${currentDayKey} (job-state resume)`, 'warn') continue } // Reset compromised state per account diff --git a/src/util/JobState.ts b/src/util/JobState.ts index e515ac0..c839b9b 100644 --- a/src/util/JobState.ts +++ b/src/util/JobState.ts @@ -99,6 +99,10 @@ export class JobState { this.save(email, st) } } + + getJobStateDir(): string { + return this.baseDir + } } export default JobState