diff --git a/docs/accounts.md b/docs/accounts.md index 1a9b757..2b949ee 100644 --- a/docs/accounts.md +++ b/docs/accounts.md @@ -21,7 +21,7 @@ } ``` -> ℹ️ `recoveryEmail` is **mandatory**. It lets the bot verify Microsoft’s masked hint during login and alert you if the recovery address ever changes. +> ℹ️ `recoveryEmail` is still **recommended**. It lets the bot verify Microsoft’s masked hint during login and alert you if the recovery address ever changes. You can opt out per account by setting `"recoveryRequired": false`. **That's it!** Run `npm start` to test. @@ -60,6 +60,26 @@ --- +## 🚫 Skip the Recovery Email Check (Advanced) + +Most users should keep recovery validation enabled. If an account genuinely has no recovery address or you cannot disclose it, add `"recoveryRequired": false` to that entry. When disabled, the bot skips mismatch detection and logs a warning during startup validation. + +```json +{ + "accounts": [ + { + "email": "example@outlook.com", + "password": "strong_password", + "recoveryRequired": false + } + ] +} +``` + +> ⚠️ Without a recovery email the bot cannot detect if Microsoft shows a different masked address. Enable this override only when you accept that risk. + +--- + ## 🎯 Multiple Accounts ```json diff --git a/docs/config-reference.md b/docs/config-reference.md new file mode 100644 index 0000000..44faa8c --- /dev/null +++ b/docs/config-reference.md @@ -0,0 +1,166 @@ +# Configuration Reference + +This page mirrors the defaults that ship in `src/config.jsonc` and explains what each field does. Use it as a companion after trimming comments from the JSONC file. + +--- + +## General + +| Key | Default | Notes | +| --- | --- | --- | +| `baseURL` | `https://rewards.bing.com` | Microsoft Rewards dashboard root. | +| `sessionPath` | `sessions` | Folder for cookies, fingerprints, and job-state. | +| `dryRun` | `false` | Log actions without executing tasks. + +--- + +## Browser & Fingerprinting + +| Key | Default | Notes | +| --- | --- | --- | +| `browser.headless` | `false` | Use `true` for CI or servers. | +| `browser.globalTimeout` | `"30s"` | Accepts milliseconds or readable strings (e.g. `"2min"`). | +| `fingerprinting.saveFingerprint.desktop` | `true` | Persist desktop fingerprint between runs. | +| `fingerprinting.saveFingerprint.mobile` | `true` | Persist mobile fingerprint. + +--- + +## Execution & Job State + +| Key | Default | Notes | +| --- | --- | --- | +| `execution.parallel` | `false` | Run desktop and mobile simultaneously. | +| `execution.runOnZeroPoints` | `false` | Skip account when no points remain. | +| `execution.clusters` | `1` | Worker processes. Increase for parallel accounts. | +| `execution.passesPerRun` | `1` | Extra full passes. Keep at `1` to allow job-state skipping. | +| `jobState.enabled` | `true` | Persist daily completion markers. | +| `jobState.dir` | `""` | Custom job-state directory (defaults under `sessionPath`). + +> Raising `passesPerRun` intentionally prevents job-state from skipping finished accounts. + +--- + +## Scheduler + +| Key | Default | Notes | +| --- | --- | --- | +| `schedule.enabled` | `false` | Enable built-in scheduler loop. | +| `schedule.useAmPm` | `false` | Toggle between `time12` (12h) and `time24` (24h). | +| `schedule.time12` | `"9:00 AM"` | Used when `useAmPm` is `true`. | +| `schedule.time24` | `"09:00"` | Used when `useAmPm` is `false`. | +| `schedule.timeZone` | `Europe/Paris` | IANA timezone for scheduling. | +| `schedule.runImmediatelyOnStart` | `true` | Execute one pass right after startup. | +| `schedule.cron` | - | Optional cron expression(s). + +See `docs/schedule.md` for jitter, cron patterns, and vacation integration. + +--- + +## Workers + +| Key | Default | Notes | +| --- | --- | --- | +| `workers.doDailySet` | `true` | Daily set. | +| `workers.doMorePromotions` | `true` | Extra promotions. | +| `workers.doPunchCards` | `true` | Punch cards. | +| `workers.doDesktopSearch` | `true` | Desktop search tasks. | +| `workers.doMobileSearch` | `true` | Mobile search tasks. | +| `workers.doDailyCheckIn` | `true` | Mobile check-in. | +| `workers.doReadToEarn` | `true` | Read-to-earn. | +| `workers.bundleDailySetWithSearch` | `true` | Launch desktop search immediately after the daily set. + +--- + +## Search & Diversity + +| Key | Default | Notes | +| --- | --- | --- | +| `search.useLocalQueries` | `true` | Use region-aware query pools. | +| `search.settings.useGeoLocaleQueries` | `true` | Inject account country into generated queries. | +| `search.settings.scrollRandomResults` | `true` | Random scrolls for realism. | +| `search.settings.clickRandomResults` | `true` | Occasional safe click-through. | +| `search.settings.retryMobileSearchAmount` | `2` | Retries for mobile search failures. | +| `search.settings.delay.min/max` | `3min` / `5min` | Delay between searches. | +| `queryDiversity.enabled` | `true` | Combine multiple content sources. | +| `queryDiversity.sources` | `["google-trends", "reddit", "local-fallback"]` | Active diversity sources. | +| `queryDiversity.maxQueriesPerSource` | `10` | Cap per source. | +| `queryDiversity.cacheMinutes` | `30` | Cache lifetime in minutes. + +--- + +## Humanization & Vacation + +| Key | Default | Notes | +| --- | --- | --- | +| `humanization.enabled` | `true` | Master toggle. | +| `humanization.stopOnBan` | `true` | Halt remaining accounts after a ban. | +| `humanization.immediateBanAlert` | `true` | Send alert instantly on ban. | +| `humanization.actionDelay.min/max` | `500` / `2200` | Extra wait between steps (ms). | +| `humanization.gestureMoveProb` | `0.65` | Chance of micro mouse move. | +| `humanization.gestureScrollProb` | `0.4` | Chance of small scroll. | +| `humanization.allowedWindows` | `[]` | Optional `HH:mm-HH:mm` windows. | +| `vacation.enabled` | `true` | Random monthly break. | +| `vacation.minDays` / `maxDays` | `2` / `4` | Range for skipped days. + +--- + +## Risk Management & Retries + +| Key | Default | Notes | +| --- | --- | --- | +| `riskManagement.enabled` | `true` | Adaptive risk scoring. | +| `riskManagement.autoAdjustDelays` | `true` | Increase delays on high risk. | +| `riskManagement.stopOnCritical` | `false` | Stop automation at critical risk. | +| `riskManagement.banPrediction` | `true` | Estimate ban likelihood. | +| `riskManagement.riskThreshold` | `75` | Alert threshold (0-100). | +| `retryPolicy.maxAttempts` | `3` | Generic retry attempts. | +| `retryPolicy.baseDelay` | `1000` | Initial backoff delay (ms or string). | +| `retryPolicy.maxDelay` | `"30s"` | Maximum backoff delay. | +| `retryPolicy.multiplier` | `2` | Backoff multiplier. | +| `retryPolicy.jitter` | `0.2` | Adds randomness to delays. + +--- + +## Networking & Notifications + +| Key | Default | Notes | +| --- | --- | --- | +| `proxy.proxyGoogleTrends` | `true` | Route Google Trends calls through the proxy. | +| `proxy.proxyBingTerms` | `true` | Route Bing requests through the proxy. | +| `webhook.enabled` | `false` | Live logs webhook. | +| `conclusionWebhook.enabled` | `false` | Summary webhook. | +| `ntfy.enabled` | `false` | Push notifications via NTFY. | +| `logging.excludeFunc` | `["SEARCH-CLOSE-TABS", "LOGIN-NO-PROMPT", "FLOW"]` | Buckets skipped locally. | +| `logging.webhookExcludeFunc` | same | Buckets skipped in webhook payloads. | +| `logging.redactEmails` | `true` | Mask email addresses in logs. | +| `diagnostics.enabled` | `true` | Capture screenshots/HTML on failure. | +| `diagnostics.maxPerRun` | `2` | Limit capture count per run. | +| `diagnostics.retentionDays` | `7` | Auto-clean old diagnostics. | +| `analytics.enabled` | `true` | Persist account metrics. | +| `analytics.retentionDays` | `30` | Keep analytics data for N days. | +| `analytics.exportMarkdown` | `true` | Write markdown summaries to `reports/`. | +| `analytics.webhookSummary` | `true` | Send analytics summary via webhook. + +--- + +## Buy Mode & Updates + +| Key | Default | Notes | +| --- | --- | --- | +| `buyMode.maxMinutes` | `45` | Session length cap when using `-buy`. | +| `update.git` | `true` | Run git updater after completion. | +| `update.docker` | `false` | Use Docker updater instead. | +| `update.scriptPath` | `setup/update/update.mjs` | Update script path. | +| `update.autoUpdateConfig` | `true` | Merge upstream config changes with backups. | +| `update.autoUpdateAccounts` | `false` | Skip account template merges unless you opt in. + +--- + +### Recommended Workflow + +1. Start from the default config and copy it if you need a local override. +2. Leave `passesPerRun` at `1` so job-state can skip accounts automatically. +3. Enable the scheduler only after testing manual runs. +4. Document any changes you make (without storing credentials in git). + +Related docs: [`accounts.md`](./accounts.md), [`schedule.md`](./schedule.md), [`proxy.md`](./proxy.md), [`humanization.md`](./humanization.md), [`security.md`](./security.md). diff --git a/docs/config.md b/docs/config.md index 4664b28..d939ea3 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,5 +1,7 @@ # ⚙️ Configuration Guide# ⚙️ Configuration Guide +> Looking for the stripped-down key-by-key table? See [`config-reference.md`](./config-reference.md). + **Customize script behavior in `src/config.jsonc`**This page documents every field in the configuration file. The default ships as `src/config.jsonc` so you get inline `//` guidance without editor warnings, and the loader still accepts traditional `config.json` files if you prefer plain JSON. diff --git a/src/accounts.example.jsonc b/src/accounts.example.jsonc index 2861c74..732a0ac 100644 --- a/src/accounts.example.jsonc +++ b/src/accounts.example.jsonc @@ -1,148 +1,26 @@ { - // ============================================================ - // 📧 MICROSOFT ACCOUNTS CONFIGURATION - // ============================================================ - - // ⚠️ IMPORTANT SECURITY NOTICE - // This file contains sensitive credentials. Never commit the real accounts.jsonc to version control. - // The .gitignore is configured to exclude accounts.jsonc but you should verify it's not tracked. - - // 📊 MICROSOFT ACCOUNT LIMITS (Unofficial Guidelines) - // - New accounts per IP per day: ~3 (official soft limit) - // - Recommended active accounts per household IP: ~5 (to avoid suspicion) - // - Creating too many accounts quickly may trigger verification (phone, OTP, captcha) - // - Unusual activity can result in temporary blocks or account restrictions - + // Sample accounts configuration. Copy to accounts.jsonc and fill in real values. "accounts": [ { - // ============================================================ - // 👤 ACCOUNT 1 - // ============================================================ - - // Enable or disable this account (true = active, false = skip) "enabled": true, - - // Microsoft account email address "email": "email_1@outlook.com", - - // Account password "password": "password_1", - - // Two-Factor Authentication (2FA) TOTP secret (optional but HIGHLY recommended for security) - // Steps: - // 1. Visit https://account.live.com/proofs/Manage/additional and enable two-step verification. - // 2. When the wizard appears, click the blue link "Set up a different authenticator app". - // 3. On the next page click "I can't scan the bar code" to reveal the Base32 secret (letters + numbers). - // 4. Scan the QR with your authenticator (Google Authenticator recommended) AND copy the secret shown. - // 5. Paste the secret here. The same secret can stay in your app and power the bot simultaneously. - // Format: Base32 secret key (e.g., "JBSWY3DPEHPK3PXP"). Leave empty "" if 2FA is not enabled. "totp": "", - - // ⚠️ REQUIRED: Recovery email address associated with this Microsoft account - // During login, Microsoft shows the first 2 characters and the domain of the recovery email (e.g., "ab***@example.com") - // This field is MANDATORY to detect account compromise or bans: - // - The script compares what Microsoft displays with this configured recovery email - // - If they don't match, it alerts you that the account may be compromised or the recovery email was changed - // - This security check helps identify hijacked accounts before they cause issues - // Format: Full recovery email address (e.g., "backup@gmail.com") - "recoveryEmail": "your_email@domain.com", - - // ============================================================ - // 🌐 PROXY CONFIGURATION (Optional) - // ============================================================ - + "recoveryEmail": "backup_1@example.com", "proxy": { - // Enable proxy for HTTP requests (axios/API calls) - // If false, proxy is only used for browser automation "proxyAxios": true, - - // Proxy server URL (protocol optional) - // Examples: "proxy.example.com", "http://proxy.example.com", "socks5://proxy.example.com" - // Leave empty "" to disable proxy for this account "url": "", - - // Proxy port number "port": 0, - - // Proxy authentication username (leave empty if no auth required) "username": "", - - // Proxy authentication password (leave empty if no auth required) "password": "" } }, - { - // ============================================================ - // 👤 ACCOUNT 2 - // ============================================================ - "enabled": false, "email": "email_2@outlook.com", "password": "password_2", "totp": "", - "recoveryEmail": "your_email@domain.com", - - "proxy": { - "proxyAxios": true, - "url": "", - "port": 0, - "username": "", - "password": "" - } - }, - - { - // ============================================================ - // 👤 ACCOUNT 3 - // ============================================================ - - "enabled": false, - "email": "email_3@outlook.com", - "password": "password_3", - "totp": "", - "recoveryEmail": "your_email@domain.com", - - "proxy": { - "proxyAxios": true, - "url": "", - "port": 0, - "username": "", - "password": "" - } - }, - - { - // ============================================================ - // 👤 ACCOUNT 4 - // ============================================================ - - "enabled": false, - "email": "email_4@outlook.com", - "password": "password_4", - "totp": "", - "recoveryEmail": "your_email@domain.com", - - "proxy": { - "proxyAxios": true, - "url": "", - "port": 0, - "username": "", - "password": "" - } - }, - - { - // ============================================================ - // 👤 ACCOUNT 5 - // ============================================================ - - "enabled": false, - "email": "email_5@outlook.com", - "password": "password_5", - "totp": "", - "recoveryEmail": "your_email@domain.com", - + "recoveryRequired": false, "proxy": { "proxyAxios": true, "url": "", diff --git a/src/config.jsonc b/src/config.jsonc index 926d1fe..683c7fc 100644 --- a/src/config.jsonc +++ b/src/config.jsonc @@ -1,237 +1,133 @@ { - // ============================================================ - // 🌐 GENERAL CONFIGURATION - // ============================================================ - - // Base URL for Microsoft Rewards dashboard (do not change unless necessary) + // General "baseURL": "https://rewards.bing.com", - - // Directory to store sessions (cookies, browser fingerprints) "sessionPath": "sessions", - - // Dry-run mode: simulate execution without actually running tasks (useful for testing) "dryRun": false, - - - // ============================================================ - // 🖥️ BROWSER CONFIGURATION - // ============================================================ - + + // Browser "browser": { - // false = visible window | true = headless mode (invisible) "headless": false, - // Max timeout for operations (supports: 30000, "30s", "2min") "globalTimeout": "30s" }, - "fingerprinting": { - // Persist browser fingerprints to improve consistency across runs "saveFingerprint": { "mobile": true, "desktop": true } }, - - - // ============================================================ - // ⚙️ EXECUTION & PERFORMANCE - // ============================================================ - + + // Execution "execution": { - // true = Desktop + Mobile in parallel (faster, more resources) - // false = Sequential (slower, fewer resources) "parallel": false, - // If false, skip execution when 0 points are available "runOnZeroPoints": false, - // Number of account clusters (processes) to run concurrently "clusters": 1, - // How many times to run through all accounts in sequence (1 = process each account once, 2 = twice, etc.) - // Higher values can catch missed tasks but increase detection risk - "passesPerRun": 3 + "passesPerRun": 1 }, - "schedule": { - // Built-in scheduler (no cron needed in containers) "enabled": false, - // Time format options: - // - US style with AM/PM → useAmPm: true and time12 (e.g., "9:00 AM") - // - 24-hour style → useAmPm: false and time24 (e.g., "09:00") "useAmPm": false, "time12": "9:00 AM", "time24": "09:00", - // IANA timezone (e.g., "Europe/Paris", "America/New_York" check schedule.md) "timeZone": "Europe/Paris", - // If true, run immediately on process start "runImmediatelyOnStart": true }, - "jobState": { - // Save state to avoid duplicate work across restarts "enabled": true, - // Custom state directory (empty = defaults to sessionPath/job-state) "dir": "" }, - - - // ============================================================ - // 🎯 TASKS & WORKERS - // ============================================================ - + + // Tasks "workers": { - // Select which tasks the bot should complete on desktop/mobile - "doDailySet": true, // Daily set tasks - "doMorePromotions": true, // More promotions section - "doPunchCards": true, // Punch cards - "doDesktopSearch": true, // Desktop searches - "doMobileSearch": true, // Mobile searches - "doDailyCheckIn": true, // Daily check-in - "doReadToEarn": true, // Read to earn - // If true, run desktop searches right after Daily Set + "doDailySet": true, + "doMorePromotions": true, + "doPunchCards": true, + "doDesktopSearch": true, + "doMobileSearch": true, + "doDailyCheckIn": true, + "doReadToEarn": true, "bundleDailySetWithSearch": true }, - - - // ============================================================ - // 🔍 SEARCH CONFIGURATION - // ============================================================ - + + // Search "search": { - // Use locale-specific query sources "useLocalQueries": true, "settings": { - // Use region-specific queries (at, fr, us, etc.) "useGeoLocaleQueries": true, - // Randomly scroll search result pages (more natural behavior) "scrollRandomResults": true, - // Occasionally click a result (safe targets only) "clickRandomResults": true, - // Number of retries if mobile searches don't progress "retryMobileSearchAmount": 2, - // Delay between searches "delay": { "min": "3min", "max": "5min" } } }, - "queryDiversity": { - // Multi-source query generation: Reddit, News, Wikipedia instead of only Google Trends "enabled": true, - // Available sources: google-trends, reddit, news, wikipedia, local-fallback "sources": ["google-trends", "reddit", "local-fallback"], - // Max queries to fetch per source "maxQueriesPerSource": 10, - // Cache duration in minutes (avoids hammering APIs) "cacheMinutes": 30 }, - - - // ============================================================ - // 🤖 HUMANIZATION & NATURAL BEHAVIOR - // ============================================================ - + + // Humanization "humanization": { - // Human Mode: adds subtle micro-gestures & pauses to mimic real users "enabled": true, - // If a ban is detected on any account, stop processing remaining accounts "stopOnBan": true, - // Immediately send an alert (webhook/NTFY) when a ban is detected "immediateBanAlert": true, - // Extra random pause between actions - "actionDelay": { - "min": 500, // 0.5 seconds minimum - "max": 2200 // 2.2 seconds maximum + "actionDelay": { + "min": 500, + "max": 2200 }, - // Probability (0-1) to move mouse slightly between actions "gestureMoveProb": 0.65, - // Probability (0-1) to perform a small scroll "gestureScrollProb": 0.4, - // Optional execution time windows (e.g., ["08:30-11:00", "19:00-22:00"]) - // If specified, waits until inside a window before starting "allowedWindows": [] }, - "vacation": { - // Monthly "vacation" block: skip a random range of days each month - // Each month, a random period between minDays and maxDays is selected - // and all runs within that date range are skipped (more human-like behavior) "enabled": true, "minDays": 2, "maxDays": 4 }, - - - // ============================================================ - // 🛡️ RISK MANAGEMENT & SECURITY - // ============================================================ - + + // Risk & retries "riskManagement": { - // Dynamic delay adjustment based on detected risk signals "enabled": true, - // Automatically increase delays when captchas/errors are detected "autoAdjustDelays": true, - // Stop execution if risk level reaches critical threshold "stopOnCritical": false, - // Enable ML-based ban prediction based on patterns "banPrediction": true, - // Risk threshold (0-100). If exceeded, bot pauses or alerts you "riskThreshold": 75 }, - "retryPolicy": { - // Generic retry/backoff for transient failures "maxAttempts": 3, "baseDelay": 1000, "maxDelay": "30s", "multiplier": 2, "jitter": 0.2 }, - - - // ============================================================ - // 🌐 PROXY - // ============================================================ - + + // Networking "proxy": { - // Control which outbound calls go through your proxy "proxyGoogleTrends": true, "proxyBingTerms": true }, - - - // ============================================================ - // 🔔 NOTIFICATIONS - // ============================================================ - - // Live logs webhook (Discord or similar). URL = your webhook endpoint + + // Notifications "webhook": { "enabled": false, "url": "" }, - - // Rich end-of-run summary webhook (Discord or similar) "conclusionWebhook": { "enabled": false, "url": "" }, - - // NTFY push notifications (plain text) "ntfy": { "enabled": false, "url": "", "topic": "rewards", "authToken": "" }, - - - // ============================================================ - // 📊 LOGGING & DIAGNOSTICS - // ============================================================ - + + // Logging & diagnostics "logging": { - // Logging controls (see docs/config.md) - // Filter out noisy log buckets locally and for webhook summaries "excludeFunc": [ "SEARCH-CLOSE-TABS", "LOGIN-NO-PROMPT", @@ -242,95 +138,33 @@ "LOGIN-NO-PROMPT", "FLOW" ], - // Email redaction toggle (true = secure, false = full emails) "redactEmails": true }, - "diagnostics": { - // Capture minimal evidence on failures (screenshots/HTML) "enabled": true, "saveScreenshot": true, "saveHtml": true, "maxPerRun": 2, "retentionDays": 7 }, - "analytics": { - // 📈 Performance Dashboard: tracks points earned, success rates, execution times - // Useful for monitoring your stats over time. Disable if you don't need it. - // WHAT IT DOES: - // - Collects daily/weekly/monthly statistics - // - Calculates success rates for each activity type - // - Tracks average execution times - // - Generates trend reports - // - Can export to Markdown or send via webhook "enabled": true, - // How long to keep analytics data (days) "retentionDays": 30, - // Generate markdown summary reports "exportMarkdown": true, - // Send analytics summary via webhook "webhookSummary": true }, - - - // ============================================================ - // 🛒 BUY MODE - // ============================================================ - + + // Buy mode "buyMode": { - // Manual purchase/redeem mode. Use CLI -buy to enable - // Session duration cap in minutes "maxMinutes": 45 }, - - - // ============================================================ - // 🔄 UPDATES - // ============================================================ - + + // Updates "update": { - // Post-run auto-update settings "git": true, "docker": false, - // Custom updater script path (relative to repo root) "scriptPath": "setup/update/update.mjs", - - // ⚠️ SMART UPDATE CONTROL - How It Really Works: - // - // BACKUP: Your files are ALWAYS backed up to .update-backup/ before any update - // - // UPDATE PROCESS: - // 1. Script checks if remote modified config.jsonc or accounts.json - // 2. Runs "git pull --rebase" to merge remote changes - // 3. Git intelligently merges: - // ✅ NEW FIELDS ADDED (new config options, new account properties) - // → Your existing values are PRESERVED, new fields are added alongside - // → This is 95% of updates - works perfectly without conflicts - // - // ⚠️ MAJOR RESTRUCTURING (fields renamed, sections reordered, format changed) - // → Git may choose one version over the other - // → Risk of losing your custom values in restructured sections - // - // WHAT THE OPTIONS DO: - // - true: ACCEPT git merge result (keeps new features + your settings in most cases) - // - false: REJECT remote changes, RESTORE your local file from backup (stay on old version) - // - // RECOMMENDED: Keep both TRUE - // Why? Because we rarely restructure files. Most updates just ADD new optional fields. - // Your passwords, emails, and custom settings survive addition-only updates. - // Only risk: major file restructuring (rare, usually announced in release notes). - // - // SAFETY NET: Check .update-backup/ folder after updates to compare if worried. - - // Apply remote updates to config.jsonc via git merge - // true = accept new features + intelligent merge (RECOMMENDED for most users) - // false = always keep your local version (miss new config options) "autoUpdateConfig": true, - - // Apply remote updates to accounts.json via git merge - // true = accept new fields (like "region", "totpSecret") while keeping credentials (RECOMMENDED) - // false = always keep your local accounts file (safest but may miss new optional fields) "autoUpdateAccounts": false } } diff --git a/src/index.ts b/src/index.ts index a15bed1..14b08ed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -643,7 +643,9 @@ export class MicrosoftRewardsBot { } } catch {/* ignore */} this.currentAccountEmail = account.email - this.currentAccountRecoveryEmail = account.recoveryEmail + this.currentAccountRecoveryEmail = (typeof account.recoveryEmail === 'string' && account.recoveryEmail.trim() !== '') + ? account.recoveryEmail.trim() + : undefined const runNumber = (this.accountRunCounts.get(account.email) ?? 0) + 1 this.accountRunCounts.set(account.email, runNumber) log('main', 'MAIN-WORKER', `Started tasks for account ${account.email}`) diff --git a/src/interface/Account.ts b/src/interface/Account.ts index cbb7672..c91d6e9 100644 --- a/src/interface/Account.ts +++ b/src/interface/Account.ts @@ -5,8 +5,10 @@ export interface Account { password: string; /** Optional TOTP secret in Base32 (e.g., from Microsoft Authenticator setup) */ totp?: string; - /** Recovery email used during security challenge verification (mandatory) */ - recoveryEmail: string; + /** Recovery email used during security challenge verification */ + recoveryEmail?: string; + /** Override to allow skipping recovery email checks for this account */ + recoveryRequired?: boolean; proxy: AccountProxy; } diff --git a/src/util/Load.ts b/src/util/Load.ts index 95c7fee..b601502 100644 --- a/src/util/Load.ts +++ b/src/util/Load.ts @@ -294,12 +294,37 @@ export function loadAccounts(): Account[] { } a.email = String(a.email).trim() a.password = String(a.password) - if (typeof a.recoveryEmail !== 'string') { - throw new Error(`account ${a.email || ''} must include a recoveryEmail string`) + const recoveryRequired = a.recoveryRequired !== false + a.recoveryRequired = recoveryRequired + + if (recoveryRequired) { + if (typeof a.recoveryEmail !== 'string') { + throw new Error(`account ${a.email || ''} must include a recoveryEmail string`) + } + a.recoveryEmail = String(a.recoveryEmail).trim() + if (!a.recoveryEmail || !/@/.test(a.recoveryEmail)) { + throw new Error(`account ${a.email} recoveryEmail must be a valid email address`) + } + } else { + if (typeof a.recoveryEmail === 'string' && a.recoveryEmail.trim() !== '') { + const trimmed = a.recoveryEmail.trim() + if (!/@/.test(trimmed)) { + throw new Error(`account ${a.email} recoveryEmail must be a valid email address`) + } + a.recoveryEmail = trimmed + } else { + a.recoveryEmail = undefined + } } - a.recoveryEmail = String(a.recoveryEmail).trim() - if (!a.recoveryEmail || !/@/.test(a.recoveryEmail)) { - throw new Error(`account ${a.email} recoveryEmail must be a valid email address`) + + if (!a.proxy || typeof a.proxy !== 'object') { + a.proxy = { proxyAxios: true, url: '', port: 0, username: '', password: '' } + } else { + a.proxy.proxyAxios = a.proxy.proxyAxios !== false + a.proxy.url = typeof a.proxy.url === 'string' ? a.proxy.url : '' + a.proxy.port = typeof a.proxy.port === 'number' ? a.proxy.port : 0 + a.proxy.username = typeof a.proxy.username === 'string' ? a.proxy.username : '' + a.proxy.password = typeof a.proxy.password === 'string' ? a.proxy.password : '' } } // Filter out disabled accounts (enabled: false) diff --git a/src/util/StartupValidator.ts b/src/util/StartupValidator.ts index 9383b0e..2bbf45a 100644 --- a/src/util/StartupValidator.ts +++ b/src/util/StartupValidator.ts @@ -91,26 +91,43 @@ export class StartupValidator { ) } - // Required: recoveryEmail (NEW - mandatory field) - if (!account.recoveryEmail || typeof account.recoveryEmail !== 'string') { - this.addError( - 'accounts', - `${prefix}: Missing required field "recoveryEmail"`, - 'Add your recovery/backup email address. This is MANDATORY for security checks.\nExample: "recoveryEmail": "backup@gmail.com"', - 'docs/accounts.md' - ) - } else if (!/@/.test(account.recoveryEmail)) { - this.addError( - 'accounts', - `${prefix}: Recovery email format is invalid`, - 'Recovery email must be a valid email address (e.g., backup@gmail.com)' - ) - } else if (account.recoveryEmail.trim() === '') { - this.addError( - 'accounts', - `${prefix}: Recovery email cannot be empty`, - 'Provide the actual recovery email associated with this Microsoft account' - ) + const recoveryRequired = account.recoveryRequired !== false + if (recoveryRequired) { + if (!account.recoveryEmail || typeof account.recoveryEmail !== 'string') { + this.addError( + 'accounts', + `${prefix}: Missing required field "recoveryEmail"`, + 'Add your recovery/backup email address. This is required for security checks unless you explicitly disable it.\nExample: "recoveryEmail": "backup@gmail.com"', + 'docs/accounts.md' + ) + } else if (!/@/.test(account.recoveryEmail)) { + this.addError( + 'accounts', + `${prefix}: Recovery email format is invalid`, + 'Recovery email must be a valid email address (e.g., backup@gmail.com)' + ) + } else if (account.recoveryEmail.trim() === '') { + this.addError( + 'accounts', + `${prefix}: Recovery email cannot be empty`, + 'Provide the actual recovery email associated with this Microsoft account' + ) + } + } else { + if (!account.recoveryEmail || account.recoveryEmail.trim() === '') { + this.addWarning( + 'accounts', + `${prefix}: Recovery email checks disabled`, + 'The bot will skip recovery-email mismatch detection for this account. Re-enable by removing "recoveryRequired": false.', + 'docs/accounts.md' + ) + } else if (!/@/.test(account.recoveryEmail)) { + this.addError( + 'accounts', + `${prefix}: Recovery email format is invalid`, + 'Recovery email must be a valid email address (e.g., backup@gmail.com)' + ) + } } // Optional but recommended: TOTP