From abd6117db3c2244b1377ddde9f5d7d7307fd4a91 Mon Sep 17 00:00:00 2001
From: Light <123307773+LightZirconite@users.noreply.github.com>
Date: Thu, 16 Oct 2025 17:59:53 +0200
Subject: [PATCH] V2.3.0 Optimization (#380)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Updated README.md to reflect version 2.1 and improve the presentation of Microsoft Rewards Automation features.
* Updated version to 2.1.5 in README.md and package.json, added new license and legal notice sections, and improved the configuration script for a better user experience.
* Mise à jour des messages de journalisation et ajout de vérifications pour le chargement des quiz et la présence des options avant de procéder. Suppression de fichiers de configuration obsolètes.
* Added serial protection dialog management for message forwarding, including closing by button or escape.
* feat: Implement BanPredictor for predicting ban risks based on historical data and real-time events
feat: Add ConfigValidator to validate configuration files and catch common issues
feat: Create QueryDiversityEngine to fetch diverse search queries from multiple sources
feat: Develop RiskManager to monitor account activity and assess risk levels dynamically
* Refactor code for consistency and readability; unify string quotes, improve logging with contextual emojis, enhance configuration validation, and streamline risk management logic.
* feat: Refactor BrowserUtil and Login classes for improved button handling and selector management; implement unified selector system and enhance activity processing logic in Workers class.
* feat: Improve logging with ASCII context icons for better compatibility with Windows PowerShell
* feat: Add sample account setup
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* feat: Update Node.js engine requirement to >=20.0.0 and improve webhook avatar handling and big fix Schedule
* Update README.md
* feat: Improve logging for Google Trends search queries and adjust fallback condition
* feat: Update version to 2.2.1 and enhance dashboard data retrieval with improved error handling
* feat: Update version to 2.2.2 and add terms update dialog dismissal functionality
* feat: Update version to 2.2.2 and require Node.js engine >=20.0.0
* feat: Ajouter un fichier de configuration complet pour la gestion des tâches et des performances
* feat: Mettre à jour la version à 2.2.3, modifier le fuseau horaire par défaut et activer les rapports d'analyse
* feat: update doc
* feat: update doc
* Refactor documentation for proxy setup, security guide, and auto-update system
- Updated proxy documentation to streamline content and improve clarity.
- Revised security guide to emphasize best practices and incident response.
- Simplified auto-update documentation, enhancing user understanding of the update process.
- Removed redundant sections and improved formatting for better readability.
* feat: update version to 2.2.7 in package.json
* feat: update version to 2.2.7 in README.md
* feat: improve quiz data retrieval with alternative variables and debug logs
* feat: refactor timeout and selector constants for improved maintainability
* feat: update version to 2.2.8 in package.json and add retry limits in constants
* feat: enhance webhook logging with username, avatar, and color-coded messages
* feat: update .gitignore to include diagnostic folder and bump version to 2.2.8 in package-lock.json
* feat: updated version to 2.3.0 and added new constants to improve the handling of delays and colors in logs
---
.gitignore | 1 +
README.md | 12 +-
docs/accounts.md | 209 ++++--
docs/buy-mode.md | 210 ++----
docs/conclusionwebhook.md | 351 ++--------
docs/config.md | 800 ++++++++++++++++-------
docs/diagnostics.md | 216 ++----
docs/docker.md | 191 ++++--
docs/getting-started.md | 206 +++---
docs/humanization.md | 334 +++-------
docs/index.md | 107 +--
docs/jobstate.md | 355 ++--------
docs/ntfy.md | 421 ++----------
docs/proxy.md | 623 ++----------------
docs/schedule.md | 692 +++-----------------
docs/security.md | 323 ++++-----
docs/update.md | 431 ++----------
package-lock.json | 6 +-
package.json | 4 +-
src/browser/BrowserFunc.ts | 190 ++++--
src/browser/BrowserUtil.ts | 36 +
src/config.jsonc | 304 +++++----
src/constants.ts | 67 ++
src/functions/Login.ts | 45 +-
src/functions/activities/ABC.ts | 23 +-
src/functions/activities/Poll.ts | 9 +-
src/functions/activities/Quiz.ts | 17 +-
src/functions/activities/Search.ts | 6 +-
src/functions/activities/SearchOnBing.ts | 15 +-
src/functions/activities/ThisOrThat.ts | 5 +-
src/functions/activities/UrlReward.ts | 2 +-
src/index.ts | 192 ++----
src/util/ConclusionWebhook.ts | 23 +-
src/util/Load.ts | 3 +-
src/util/Logger.ts | 52 +-
src/util/Ntfy.ts | 10 +-
src/util/QueryDiversityEngine.ts | 5 +-
37 files changed, 2392 insertions(+), 4104 deletions(-)
create mode 100644 src/constants.ts
diff --git a/.gitignore b/.gitignore
index 79a3071..a4ee868 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ dist/
node_modules/
.vscode/
.github/
+diagnostic/
accounts.json
notes
accounts.dev.json
diff --git a/README.md b/README.md
index 52b8815..88742cf 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@
-
+



@@ -466,16 +466,10 @@ See [LICENSE](./LICENSE) for details • [NOTICE](./NOTICE) for disclaimers

-
+
**Made with ❤️ by the open source community**
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/docs/accounts.md b/docs/accounts.md
index 9e09db3..fb5bc9d 100644
--- a/docs/accounts.md
+++ b/docs/accounts.md
@@ -1,94 +1,159 @@
-# 👤 Accounts & TOTP (2FA)
+# 👤 Accounts & 2FA Setup
-
-
-**🔐 Secure Microsoft account setup with 2FA support**
-*Everything you need to configure authentication*
-
-
+**Add your Microsoft accounts with secure TOTP authentication**
---
-## 📍 File Location & Options
+## 📍 Quick Start
-The bot needs Microsoft account credentials to log in and complete activities. Here's how to provide them:
+### Basic Setup (No 2FA)
-### **Default Location**
-```
-src/accounts.json
-```
-
-### **Environment Overrides** (Docker/CI)
-- **`ACCOUNTS_FILE`** — Path to accounts file (e.g., `/data/accounts.json`)
-- **`ACCOUNTS_JSON`** — Inline JSON string (useful for CI/CD)
-
-The loader tries: `ACCOUNTS_JSON` → `ACCOUNTS_FILE` → default locations in project root.
-
-## Schema
-Each account has at least `email` and `password`.
-
-```
+**Edit** `src/accounts.json`:
+```json
{
"accounts": [
{
- "email": "email_1",
- "password": "password_1",
+ "email": "your@email.com",
+ "password": "your_password"
+ }
+ ]
+}
+```
+
+**That's it!** Run `npm start` to test.
+
+---
+
+## 🔐 Add 2FA/TOTP (Recommended)
+
+### Why Use TOTP?
+- ✅ **Automated login** — No manual code entry
+- ✅ **More secure** — Better than SMS
+- ✅ **Works 24/7** — Scheduler-friendly
+
+### How to Get Your TOTP Secret
+
+1. **Open Microsoft Account** → Security → Advanced security options
+2. **Add authenticator app** → Click "Set up"
+3. **Choose "I want to use a different app"**
+4. Microsoft shows a **QR code** + **secret key**
+5. **Copy the secret key** (starts with letters/numbers)
+6. **Add to** `accounts.json`:
+
+```json
+{
+ "accounts": [
+ {
+ "email": "your@email.com",
+ "password": "your_password",
+ "totp": "JBSWY3DPEHPK3PXP"
+ }
+ ]
+}
+```
+
+---
+
+## 🎯 Multiple Accounts
+
+```json
+{
+ "accounts": [
+ {
+ "email": "account1@email.com",
+ "password": "password1",
+ "totp": "SECRET1"
+ },
+ {
+ "email": "account2@email.com",
+ "password": "password2",
+ "totp": "SECRET2"
+ }
+ ]
+}
+```
+
+---
+
+## 🌐 Per-Account Proxy (Optional)
+
+```json
+{
+ "accounts": [
+ {
+ "email": "your@email.com",
+ "password": "password",
"totp": "",
- "recoveryEmail": "your_email@domain.com",
"proxy": {
"proxyAxios": true,
- "url": "",
- "port": 0,
- "username": "",
- "password": ""
+ "url": "proxy.example.com",
+ "port": 8080,
+ "username": "proxyuser",
+ "password": "proxypass"
}
}
]
}
```
-- `totp` (optional): Base32 secret for Time‑based One‑Time Passwords (2FA). If set, the bot generates the 6‑digit code automatically when asked by Microsoft.
-- `recoveryEmail` (optional): used to validate masked recovery prompts.
-- `proxy` (optional): per‑account proxy config. See the [Proxy guide](./proxy.md).
-
-## How to get your TOTP secret
-1) In your Microsoft account security settings, add an authenticator app.
-2) When shown the QR code, choose the option to enter the code manually — this reveals the Base32 secret.
-3) Copy that secret (only the text after `secret=` if you have an otpauth URL) into the `totp` field.
-
-Security tips:
-- Never commit real secrets to Git.
-- Prefer `ACCOUNTS_FILE` or `ACCOUNTS_JSON` in production.
-
-## Examples
-- Single account, no 2FA:
-```
-{"accounts":[{"email":"a@b.com","password":"pass","totp":"","recoveryEmail":"","proxy":{"proxyAxios":true,"url":"","port":0,"username":"","password":""}}]}
-```
-
-- Single account with TOTP secret:
-```
-{"accounts":[{"email":"a@b.com","password":"pass","totp":"JBSWY3DPEHPK3PXP","recoveryEmail":"","proxy":{"proxyAxios":true,"url":"","port":0,"username":"","password":""}}]}
-```
-
-- Multiple accounts:
-```
-{"accounts":[
- {"email":"a@b.com","password":"pass","totp":"","recoveryEmail":"" ,"proxy":{"proxyAxios":true,"url":"","port":0,"username":"","password":""}},
- {"email":"c@d.com","password":"pass","totp":"","recoveryEmail":"" ,"proxy":{"proxyAxios":true,"url":"","port":0,"username":"","password":""}}
-]}
-```
-
-## Troubleshooting
-- “accounts file not found”: ensure the file exists, or set `ACCOUNTS_FILE` to the correct path.
-- 2FA prompt not filled: verify `totp` is a valid Base32 secret; time on the host/container should be correct.
-- Locked account: the bot will log and skip; resolve manually then re‑enable.
+→ **[Full Proxy Guide](./proxy.md)**
---
-## 🔗 Related Guides
+## 🔒 Environment Variables (Docker/CI)
-- **[Getting Started](./getting-started.md)** — Initial setup and configuration
-- **[Docker](./docker.md)** — Container deployment with accounts
-- **[Security](./security.md)** — Account protection and incident response
-- **[NTFY Notifications](./ntfy.md)** — Get alerts for login issues
\ No newline at end of file
+### Option 1: File Path
+```bash
+export ACCOUNTS_FILE=/path/to/accounts.json
+```
+
+### Option 2: Inline JSON
+```bash
+export ACCOUNTS_JSON='{"accounts":[{"email":"test@example.com","password":"pass"}]}'
+```
+
+---
+
+## 🛠️ Troubleshooting
+
+| Problem | Solution |
+|---------|----------|
+| **"accounts.json not found"** | Create file or set `ACCOUNTS_FILE` env var |
+| **"2FA prompt not auto-filled"** | Check TOTP secret is valid Base32 |
+| **"Invalid TOTP"** | Verify system time is correct |
+| **"Account locked"** | Manually unlock in Microsoft Account |
+| **"Login timeout"** | Check internet connection, try proxy |
+
+### 2FA Not Working?
+
+1. **Check secret format** — Should be Base32 (only letters/numbers, no spaces)
+2. **Verify system time** — Must be accurate (NTP sync)
+3. **Test manually** — Use authenticator app to verify code works
+4. **Remove backup codes** — Some security settings block TOTP
+
+---
+
+## 🔒 Security Tips
+
+- 🔐 **Use strong passwords** — Unique for each account
+- 🔑 **Enable TOTP** — More secure than SMS
+- 📁 **Restrict file permissions** — `chmod 600 accounts.json` (Linux)
+- 🔄 **Rotate passwords** — Change every 90 days
+- 🚫 **Never commit** — Add `accounts.json` to `.gitignore`
+
+---
+
+## 📚 Next Steps
+
+**TOTP setup?**
+→ **[Security Guide](./security.md)** for best practices
+
+**Ready for automation?**
+→ **[Scheduler Setup](./schedule.md)**
+
+**Need proxies?**
+→ **[Proxy Guide](./proxy.md)**
+
+---
+
+**[← Back to Hub](./index.md)** | **[Getting Started](./getting-started.md)**
diff --git a/docs/buy-mode.md b/docs/buy-mode.md
index d12e2a3..8f29b3f 100644
--- a/docs/buy-mode.md
+++ b/docs/buy-mode.md
@@ -1,64 +1,53 @@
# 💳 Buy Mode
-
-
-**🛒 Manual redemption with live point monitoring**
-*Track your spending while maintaining full control*
-
-
+**Manually redeem rewards while monitoring points**
---
-## 🎯 What is Buy Mode?
+## 💡 What Is It?
-Buy Mode allows you to **manually redeem rewards** while the script **passively monitors** your point balance. Perfect for safe redemptions without automation interference.
+Launches browser and **passively monitors** your points balance while you manually shop/redeem.
-> ℹ️ Buy Mode automatically launches the browser in a visible window (headless=false) so you can interact with captchas and checkout flows. Use `FORCE_HEADLESS=1` only if you understand the limitations.
-
-### **Key Features**
-- 👀 **Passive monitoring** — No clicks or automation
-- 🔄 **Real-time tracking** — Instant spending alerts
-- 📱 **Live notifications** — Discord/NTFY integration
-- ⏱️ **Configurable duration** — Set your own time limit
-- 📊 **Session summary** — Complete spending report
+**Use case:** Safely redeem gift cards without automation interference.
---
-## 🚀 How to Use
+## ⚡ Quick Start
-### **Command Options**
```bash
-# Monitor specific account
npm start -- -buy your@email.com
-
-# Monitor first account in accounts.json
-npm start -- -buy
-
-# Alternative: Enable in config (see below)
```
-### **What Happens Next**
-1. **🖥️ Dual Tab System Opens**
- - **Monitor Tab** — Background monitoring (auto-refresh)
- - **User Tab** — Your control for redemptions/browsing
+**What happens:**
+1. Opens 2 browser tabs:
+ - **Monitor tab** — Background point tracking (auto-refresh)
+ - **Your tab** — Use this for manual purchases
+2. Monitors points every ~10 seconds
+3. Alerts you when spending detected
-2. **📊 Passive Point Tracking**
- - Reads balance every ~10 seconds
- - Detects spending when points decrease
- - Zero interference with your browsing
+---
-3. **🔔 Real-time Alerts**
- - Instant notifications when spending detected
- - Shows amount spent + current balance
- - Tracks cumulative session spending
+## 🎯 Example Usage
+
+### Redeem Gift Card
+
+```bash
+npm start -- -buy myaccount@outlook.com
+```
+
+1. Script opens Microsoft Rewards in browser
+2. Use the **user tab** to browse and redeem
+3. **Monitor tab** tracks your balance in background
+4. Get notification when points decrease
---
## ⚙️ Configuration
-### **Set Duration in Config**
-Add to `src/config.jsonc`:
-```json
+**Set max session time:**
+
+**Edit** `src/config.jsonc`:
+```jsonc
{
"buyMode": {
"enabled": false,
@@ -67,142 +56,53 @@ Add to `src/config.jsonc`:
}
```
-| Setting | Default | Description |
-|---------|---------|-------------|
-| `enabled` | `false` | Force buy mode without CLI flag |
-| `maxMinutes` | `45` | Auto-stop after N minutes |
-
-### **Enable Notifications**
-Buy mode works with existing notification settings:
-```json
-{
- "conclusionWebhook": {
- "enabled": true,
- "url": "https://discord.com/api/webhooks/YOUR_URL"
- },
- "ntfy": {
- "enabled": true,
- "url": "https://ntfy.sh",
- "topic": "rewards"
- }
-}
-```
-
---
-## 🖥️ Terminal Output
+## 🔔 Notifications
-### **Startup**
+Buy mode sends alerts when:
+- 💳 **Points spent** — Shows amount and new balance
+- 📉 **Balance changes** — Tracks cumulative spending
+
+**Example alert:**
```
- ███╗ ███╗███████╗ ██████╗ ██╗ ██╗██╗ ██╗
- ████╗ ████║██╔════╝ ██╔══██╗██║ ██║╚██╗ ██╔╝
- ██╔████╔██║███████╗ ██████╔╝██║ ██║ ╚████╔╝
- ██║╚██╔╝██║╚════██║ ██╔══██╗██║ ██║ ╚██╔╝
- ██║ ╚═╝ ██║███████║ ██████╔╝╚██████╔╝ ██║
- ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝
-
- Manual Purchase Mode • Passive Monitoring
-
-[BUY-MODE] Opening dual-tab system for safe redemptions...
-[BUY-MODE] Monitor tab: Background point tracking
-[BUY-MODE] User tab: Your control for purchases/browsing
+💳 Spend detected (Buy Mode)
+Account: user@email.com
+Spent: -500 points
+Current: 12,500 points
+Session spent: 1,200 points
```
-### **Live Monitoring**
-```
-[BUY-MODE] Current balance: 15,000 points
-[BUY-MODE] 🛒 Spending detected: -500 points (new balance: 14,500)
-[BUY-MODE] Session total spent: 500 points
-```
-
----
-
-## 📋 Use Cases
-
-| Scenario | Benefit |
-|----------|---------|
-| **🎁 Gift Card Redemption** | Track exact point cost while redeeming safely |
-| **🛍️ Microsoft Store Purchases** | Monitor spending across multiple items |
-| **✅ Account Verification** | Ensure point changes match expected activity |
-| **📊 Spending Analysis** | Real-time tracking of reward usage patterns |
-| **🔒 Safe Browsing** | Use Microsoft Rewards normally with monitoring |
-
---
## 🛠️ Troubleshooting
| Problem | Solution |
|---------|----------|
-| **Monitor tab closes** | Script auto-reopens in background |
-| **No spending alerts** | Check webhook/NTFY config; verify notifications enabled |
+| **Monitor tab closes** | Script auto-reopens it |
+| **No spending alerts** | Check webhook/NTFY config |
| **Session too short** | Increase `maxMinutes` in config |
-| **Login failures** | Verify account credentials in `accounts.json` |
-| **Points not updating** | Check internet connection; try refresh |
---
-## 🔗 Related Guides
+## ⚠️ Important Notes
-- **[Getting Started](./getting-started.md)** — Initial setup and configuration
-- **[Accounts & 2FA](./accounts.md)** — Microsoft account setup
-- **[NTFY Notifications](./ntfy.md)** — Mobile push alerts
-- **[Discord Webhooks](./conclusionwebhook.md)** — Server notifications
+- ✅ **Browser visible** — Always runs in visible mode
+- ✅ **No automation** — Script only monitors, never clicks
+- ✅ **Safe** — Use your browsing tab normally
+- ✅ **Notifications** — Uses existing webhook/NTFY settings
-## Terminal Output
+---
-When you start buy mode, you'll see:
+## 📚 Next Steps
-```
- ███╗ ███╗███████╗ ██████╗ ██╗ ██╗██╗ ██╗
- ████╗ ████║██╔════╝ ██╔══██╗██║ ██║╚██╗ ██╔╝
- ██╔████╔██║███████╗ ██████╔╝██║ ██║ ╚████╔╝
- ██║╚██╔╝██║╚════██║ ██╔══██╗██║ ██║ ╚██╔╝
- ██║ ╚═╝ ██║███████║ ██████╔╝╚██████╔╝ ██║
- ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝
-
- Manual Purchase Mode • Passive Monitoring
+**Setup notifications?**
+→ **[Discord Webhooks](./conclusionwebhook.md)**
+→ **[NTFY Push](./ntfy.md)**
-[BUY-MODE] Buy mode ENABLED for your@email.com. We'll open 2 tabs:
- (1) monitor tab (auto-refreshes), (2) your browsing tab
-[BUY-MODE] The monitor tab may refresh every ~10s. Use the other tab...
-[BUY-MODE] Opened MONITOR tab (auto-refreshes to track points)
-[BUY-MODE] Opened USER tab (use this one to redeem/purchase freely)
-[BUY-MODE] Logged in as your@email.com. Buy mode is active...
-```
+**Back to automation?**
+→ **[Getting Started](./getting-started.md)**
-During monitoring:
-```
-[BUY-MODE] Detected spend: -500 points (current: 12,500)
-[BUY-MODE] Monitor tab was closed; reopening in background...
-```
+---
-## Features
-
-- ✅ **Non-intrusive**: No clicks or navigation in your browsing tab
-- ✅ **Real-time alerts**: Instant notifications when points are spent
-- ✅ **Auto-recovery**: Reopens monitor tab if accidentally closed
-- ✅ **Webhook support**: Works with Discord and NTFY notifications
-- ✅ **Configurable duration**: Set your own monitoring time limit
-- ✅ **Session tracking**: Complete summary of spending activity
-
-## Use Cases
-
-- **Manual redemptions**: Redeem gift cards or rewards while tracking spending
-- **Account verification**: Monitor point changes during manual account activities
-- **Spending analysis**: Track how points are being used in real-time
-- **Safe browsing**: Use Microsoft Rewards normally while monitoring balance
-
-## Notes
-
-- Monitor tab runs in background and may refresh periodically
-- Your main browsing tab is completely under your control
-- Session data is saved automatically for future script runs
-- Buy mode works with existing notification configurations
-- No automation or point collection occurs in this mode
-
-## Troubleshooting
-
-- **Monitor tab closed**: Script automatically reopens it in background
-- **No notifications**: Check webhook/NTFY configuration in `config.jsonc`
-- **Session timeout**: Increase `maxMinutes` if you need longer monitoring
-- **Login issues**: Ensure account credentials are correct in `accounts.json`
+**[← Back to Hub](./index.md)** | **[Config Guide](./config.md)**
diff --git a/docs/conclusionwebhook.md b/docs/conclusionwebhook.md
index f318773..7ca46ae 100644
--- a/docs/conclusionwebhook.md
+++ b/docs/conclusionwebhook.md
@@ -1,145 +1,49 @@
-# 📊 Discord Conclusion Webhook
+# 📊 Discord Webhooks
-
-
-**🎯 Comprehensive session summaries via Discord**
-*Complete execution reports delivered instantly*
-
-
+**Get run summaries in Discord**
---
-## 🎯 What is the Conclusion Webhook?
+## 💡 What Is It?
-The conclusion webhook sends a **detailed summary notification** at the end of each script execution via Discord, providing a complete overview of the session's results across all accounts.
-
-### **Key Features**
-- 📊 **Session overview** — Total accounts processed, success/failure counts
-- 💎 **Points summary** — Starting points, earned points, final totals
-- ⏱️ **Performance metrics** — Execution times, efficiency statistics
-- ❌ **Error reporting** — Issues encountered during execution
-- 💳 **Buy mode detection** — Point spending alerts and tracking
-- 🎨 **Rich embeds** — Color-coded, well-formatted Discord messages
+Sends a **rich embed** to your Discord server after each run with:
+- 📊 Total accounts processed
+- 💎 Points earned
+- ⏱️ Execution time
+- ❌ Errors encountered
---
-## ⚙️ Configuration
+## ⚡ Quick Start
-### **Basic Setup**
-```json
+### 1. Create Webhook in Discord
+
+1. **Open Discord** → Right-click channel
+2. **Edit Channel** → **Integrations** tab
+3. **Create Webhook**
+4. **Copy webhook URL**
+
+### 2. Configure Script
+
+**Edit** `src/config.jsonc`:
+```jsonc
{
"notifications": {
"conclusionWebhook": {
"enabled": true,
- "url": "https://discord.com/api/webhooks/123456789/abcdef-webhook-token-here"
+ "url": "https://discord.com/api/webhooks/123456789/abcdef-your-webhook-token"
}
}
}
```
-### **Configuration Options**
-
-| Setting | Description | Example |
-|---------|-------------|---------|
-| `enabled` | Enable conclusion webhook | `true` |
-| `url` | Discord webhook URL | Full webhook URL from Discord |
+**That's it!** You'll get a summary after each run.
---
-## 🚀 Discord Setup
+## 📋 Example Summary
-### **Step 1: Create Webhook**
-1. **Open Discord** and go to your server
-2. **Right-click** on the channel for notifications
-3. **Select "Edit Channel"**
-4. **Go to "Integrations" tab**
-5. **Click "Create Webhook"**
-
-### **Step 2: Configure Webhook**
-- **Name** — "MS Rewards Summary"
-- **Avatar** — Upload rewards icon (optional)
-- **Channel** — Select appropriate channel
-- **Copy webhook URL**
-
-### **Step 3: Add to Config**
-```json
-{
- "notifications": {
- "conclusionWebhook": {
- "enabled": true,
- "url": "YOUR_COPIED_WEBHOOK_URL_HERE"
- }
- }
-}
```
-
----
-
-## 📋 Message Format
-
-### **Rich Embed Summary**
-
-#### **Header Section**
-```
-🎯 Microsoft Rewards Summary
-⏰ Completed at 2025-01-20 14:30:15
-📈 Total Runtime: 25m 36s
-```
-
-#### **Account Statistics**
-```
-📊 Accounts: 3 • 0 with issues
-```
-
-#### **Points Overview**
-```
-💎 Points: 15,230 → 16,890 (+1,660)
-```
-
-#### **Performance Metrics**
-```
-⏱️ Average Duration: 8m 32s
-📈 Cumulative Runtime: 25m 36s
-```
-
-#### **Buy Mode Detection** (if applicable)
-```
-💳 Buy Mode Activity Detected
-Total Spent: 1,200 points across 2 accounts
-```
-
-### **Account Breakdown**
-
-#### **Successful Account**
-```
-👤 user@example.com
-Points: 5,420 → 6,140 (+720)
-Duration: 7m 23s
-Status: ✅ Completed successfully
-```
-
-#### **Failed Account**
-```
-👤 problem@example.com
-Points: 3,210 → 3,210 (+0)
-Duration: 2m 15s
-Status: ❌ Failed - Login timeout
-```
-
-#### **Buy Mode Account**
-```
-💳 spender@example.com
-Session Spent: 500 points
-Available: 12,500 points
-Status: 💳 Purchase activity detected
-```
-
----
-
-## 📊 Message Examples
-
-### **Successful Session**
-```discord
🎯 Microsoft Rewards Summary
📊 Accounts: 3 • 0 with issues
@@ -163,97 +67,13 @@ Duration: 8m 32s
Status: ✅ Completed successfully
```
-### **Session with Issues**
-```discord
-🎯 Microsoft Rewards Summary
-
-📊 Accounts: 3 • 1 with issues
-💎 Points: 15,230 → 15,950 (+720)
-⏱️ Average Duration: 6m 15s
-📈 Cumulative Runtime: 18m 45s
-
-👤 user1@example.com
-Points: 5,420 → 6,140 (+720)
-Duration: 7m 23s
-Status: ✅ Completed successfully
-
-👤 user2@example.com
-Points: 4,810 → 4,810 (+0)
-Duration: 2m 15s
-Status: ❌ Failed - Login timeout
-
-👤 user3@example.com
-Points: 5,000 → 5,000 (+0)
-Duration: 9m 07s
-Status: ⚠️ Partially completed - Quiz failed
-```
-
-### **Buy Mode Detection**
-```discord
-🎯 Microsoft Rewards Summary
-
-📊 Accounts: 2 • 0 with issues
-💎 Points: 25,500 → 24,220 (-1,280)
-💳 Buy Mode Activity Detected
-Total Spent: 1,500 points across 1 account
-
-👤 buyer@example.com
-Points: 15,000 → 13,500 (-1,500)
-Duration: 12m 34s
-Status: 💳 Buy mode detected
-Activities: Purchase completed, searches skipped
-
-👤 normal@example.com
-Points: 10,500 → 10,720 (+220)
-Duration: 8m 45s
-Status: ✅ Completed successfully
-```
-
---
-## 🤝 Integration with Other Notifications
+## 🎯 Advanced: Separate Channels
-### **Webhook vs Conclusion Webhook**
+Use different webhooks for different notifications:
-| Feature | Real-time Webhook | Conclusion Webhook |
-|---------|------------------|-------------------|
-| **Timing** | During execution | End of session only |
-| **Content** | Errors, warnings, progress | Comprehensive summary |
-| **Frequency** | Multiple per session | One per session |
-| **Purpose** | Immediate alerts | Session overview |
-
-### **Recommended Combined Setup**
-```json
-{
- "notifications": {
- "webhook": {
- "enabled": true,
- "url": "https://discord.com/api/webhooks/.../real-time"
- },
- "conclusionWebhook": {
- "enabled": true,
- "url": "https://discord.com/api/webhooks/.../summary"
- },
- "ntfy": {
- "enabled": true,
- "url": "https://ntfy.sh",
- "topic": "rewards-mobile"
- }
- }
-}
-```
-
-### **Benefits of Combined Setup**
-- ⚡ **Real-time webhook** — Immediate error alerts
-- 📊 **Conclusion webhook** — Comprehensive session summary
-- 📱 **NTFY** — Mobile notifications for critical issues
-
----
-
-## 🎛️ Advanced Configuration
-
-### **Multiple Webhooks**
-```json
+```jsonc
{
"notifications": {
"webhook": {
@@ -268,67 +88,8 @@ Status: ✅ Completed successfully
}
```
-### **Channel Organization**
-
-#### **Recommended Discord Structure**
-- **#rewards-errors** — Real-time error notifications (webhook)
-- **#rewards-summary** — End-of-run summaries (conclusionWebhook)
-- **#rewards-logs** — Detailed text logs (manual uploads)
-
-#### **Channel Settings**
-- **Notification settings** — Configure per your preference
-- **Webhook permissions** — Limit to specific channels
-- **Message history** — Enable for tracking trends
-
----
-
-## 🔒 Security & Privacy
-
-### **Webhook Security Best Practices**
-- 🔐 Use **dedicated Discord server** for notifications
-- 🎯 **Limit permissions** to specific channels only
-- 🔄 **Regenerate URLs** if compromised
-- 🚫 **Don't share** webhook URLs publicly
-
-### **Data Transmission**
-- ✅ **Summary statistics** only
-- ✅ **Points and email** addresses
-- ❌ **No passwords** or sensitive tokens
-- ❌ **No personal information** beyond emails
-
-### **Data Retention**
-- 💾 **Discord stores** messages per server settings
-- 🗑️ **No local storage** by the script
-- ✂️ **Manual deletion** possible anytime
-- 📝 **Webhook logs** may be retained by Discord
-
----
-
-## 🧪 Testing & Debugging
-
-### **Manual Webhook Test**
-```bash
-curl -X POST \
- -H "Content-Type: application/json" \
- -d '{"content":"Test message from rewards script"}' \
- "YOUR_WEBHOOK_URL_HERE"
-```
-
-### **Script Debug Mode**
-```powershell
-$env:DEBUG_REWARDS_VERBOSE=1; npm start
-```
-
-### **Success Indicators**
-```
-[INFO] Sending conclusion webhook...
-[INFO] Conclusion webhook sent successfully
-```
-
-### **Error Messages**
-```
-[ERROR] Failed to send conclusion webhook: Invalid webhook URL
-```
+- **`webhook`** — Real-time errors during execution
+- **`conclusionWebhook`** — End-of-run summary
---
@@ -336,54 +97,26 @@ $env:DEBUG_REWARDS_VERBOSE=1; npm start
| Problem | Solution |
|---------|----------|
-| **No summary received** | Check webhook URL; verify Discord permissions |
-| **Malformed messages** | Validate webhook URL; check Discord server status |
-| **Missing information** | Ensure script completed; check for execution errors |
-| **Rate limited** | Single webhook per session prevents this |
+| **No message received** | Check webhook URL is complete |
+| **"Invalid webhook"** | Regenerate webhook in Discord |
+| **Partial data** | Ensure script completed fully |
-### **Common Fixes**
-- ✅ **Webhook URL** — Must be complete Discord webhook URL
-- ✅ **Channel permissions** — Webhook must have send permissions
-- ✅ **Server availability** — Discord server must be accessible
-- ✅ **Script completion** — Summary only sent after full execution
+### Test Webhook Manually
+
+```bash
+curl -X POST -H "Content-Type: application/json" -d '{"content":"Test message"}' "YOUR_WEBHOOK_URL"
+```
---
-## ⚡ Performance Impact
+## 📚 Next Steps
-### **Resource Usage**
-- 📨 **Single HTTP request** at script end
-- ⚡ **Non-blocking operation** — No execution delays
-- 💾 **Payload size** — Typically < 2KB
-- 🌐 **Delivery time** — Usually < 1 second
+**Want mobile alerts?**
+→ **[NTFY Push Notifications](./ntfy.md)**
-### **Benefits**
-- ✅ **No impact** on account processing
-- ✅ **Minimal memory** footprint
-- ✅ **No disk storage** required
-- ✅ **Negligible bandwidth** usage
+**Need detailed logs?**
+→ **[Diagnostics Guide](./diagnostics.md)**
---
-## 🎨 Customization
-
-### **Embed Features**
-- 🎨 **Color-coded** status indicators
-- 🎭 **Emoji icons** for visual clarity
-- 📊 **Structured fields** for easy reading
-- ⏰ **Timestamps** and duration info
-
-### **Discord Integration**
-- 💬 **Thread notifications** support
-- 👥 **Role mentions** (configure in webhook)
-- 🔍 **Searchable messages** for history
-- 📂 **Archive functionality** for records
-
----
-
-## 🔗 Related Guides
-
-- **[NTFY Notifications](./ntfy.md)** — Mobile push notifications
-- **[Getting Started](./getting-started.md)** — Initial setup and configuration
-- **[Buy Mode](./buy-mode.md)** — Manual purchasing with monitoring
-- **[Security](./security.md)** — Privacy and data protection
\ No newline at end of file
+**[← Back to Hub](./index.md)** | **[Config Guide](./config.md)**
diff --git a/docs/config.md b/docs/config.md
index 5ba7044..4664b28 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -1,283 +1,605 @@
-# ⚙️ Configuration Guide
+# ⚙️ Configuration Guide# ⚙️ Configuration Guide
-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.
-Looking for ready-to-use presets? Check `docs/config-presets/` for curated examples such as `balanced.jsonc` (full automation with humanization) and `minimal.jsonc` (lean runs with quick scheduling).
-> NOTE: Previous versions had `logging.live` (live streaming webhook); it was removed and replaced by a simple `logging.redactEmails` flag.
+**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.
+
+
+
+---Looking for ready-to-use presets? Check `docs/config-presets/` for curated examples such as `balanced.jsonc` (full automation with humanization) and `minimal.jsonc` (lean runs with quick scheduling).
+
+
+
+## ⚡ Quick Start (Essentials)> NOTE: Previous versions had `logging.live` (live streaming webhook); it was removed and replaced by a simple `logging.redactEmails` flag.
+
+
+
+### Minimal Working Config---
----
## Top-Level Fields
-### baseURL
-Internal Microsoft Rewards base. Leave it unless you know what you are doing.
+```jsonc
-### sessionPath
-Directory where session data (cookies / fingerprints / job-state) is stored.
+{### baseURL
+
+ "humanization": {Internal Microsoft Rewards base. Leave it unless you know what you are doing.
+
+ "enabled": true // Natural behavior (recommended)
+
+ },### sessionPath
+
+ "workers": {Directory where session data (cookies / fingerprints / job-state) is stored.
+
+ "doDailySet": true,
+
+ "doDesktopSearch": true,---
+
+ "doMobileSearch": true## browser
+
+ }| Key | Type | Default | Description |
+
+}|-----|------|---------|-------------|
+
+```| headless | boolean | false | Run browser UI-less. Set to `false` to keep the browser visible (default). |
----
-## browser
-| Key | Type | Default | Description |
-|-----|------|---------|-------------|
-| headless | boolean | false | Run browser UI-less. Set to `false` to keep the browser visible (default). |
| globalTimeout | string/number | "30s" | Max time for common Playwright operations. Accepts ms number or time string (e.g. `"45s"`, `"2min"`). |
+**That's all you need!** Everything else has good defaults.
+
---
-## execution
+
+---## execution
+
| Key | Type | Default | Description |
-|-----|------|---------|-------------|
+
+## 🎯 Popular Configurations|-----|------|---------|-------------|
+
| parallel | boolean | false | Run desktop + mobile simultaneously (higher resource usage). |
-| runOnZeroPoints | boolean | false | Skip full run early if there are zero points available (saves time). |
+
+### 1. Daily Automation| runOnZeroPoints | boolean | false | Skip full run early if there are zero points available (saves time). |
+
| clusters | number | 1 | Number of process clusters (multi-process concurrency). |
-| passesPerRun | number | 1 | Advanced: extra full passes per started run. |
----
-## buyMode
-Manual redeem / purchase assistance.
-| Key | Type | Default | Description |
-|-----|------|---------|-------------|
-| enabled (CLI `-buy`) | boolean | false | Enable buy mode (usually via CLI argument). |
-| maxMinutes | number | 45 | Max session length for buy mode. |
+```jsonc| passesPerRun | number | 1 | Advanced: extra full passes per started run. |
----
-## fingerprinting.saveFingerprint
-Persist browser fingerprints per device type for consistency.
-| Key | Type | Default | Description |
-|-----|------|---------|-------------|
-| mobile | boolean | false | Save/reuse a consistent mobile fingerprint. |
-| desktop | boolean | false | Save/reuse a consistent desktop fingerprint. |
-
----
-## search
-| Key | Type | Default | Description |
-|-----|------|---------|-------------|
-| useLocalQueries | boolean | false | Use locale-specific query sources instead of global ones. |
-
-### search.settings
-| Key | Type | Default | Description |
-|-----|------|---------|-------------|
-| useGeoLocaleQueries | boolean | false | Blend geo / locale into chosen queries. |
-| scrollRandomResults | boolean | true | Random scroll during search pages to look natural. |
-| clickRandomResults | boolean | true | Occasionally click safe results. |
-| retryMobileSearchAmount | number | 2 | Retries if mobile searches didn’t yield points. |
-| delay.min / delay.max | string/number | 3–5min | Delay between searches (ms or time string). |
-
----
-## humanization
-Human‑like behavior simulation.
-| Key | Type | Default | Description |
-|-----|------|---------|-------------|
-| enabled | boolean | true | Global on/off. |
-| stopOnBan | boolean | true | Stop processing further accounts if a ban is detected. |
-| immediateBanAlert | boolean | true | Fire notification immediately upon ban detection. |
-| actionDelay.min/max | number/string | 150–450ms | Random micro-delay per action. |
-| gestureMoveProb | number | 0.4 | Probability of a small mouse move gesture. |
-| gestureScrollProb | number | 0.2 | Probability of a small scroll gesture. |
-| allowedWindows | string[] | [] | Local time windows (e.g. `["08:30-11:00","19:00-22:00"]`). Outside windows, run waits. |
-
----
-## vacation
-Random contiguous block of days off per month.
-| Key | Type | Default | Description |
-|-----|------|---------|-------------|
-| enabled | boolean | false | Activate monthly break behavior. |
-| minDays | number | 3 | Minimum skipped days per month. |
-| maxDays | number | 5 | Maximum skipped days per month. |
-
----
-## retryPolicy
-Generic transient retry/backoff.
-| Key | Type | Default | Description |
-|-----|------|---------|-------------|
-| maxAttempts | number | 3 | Max tries for retryable blocks. |
-| baseDelay | number | 1000 | Initial delay in ms. |
-| maxDelay | number/string | 30s | Max backoff delay. |
-| multiplier | number | 2 | Exponential backoff multiplier. |
-| jitter | number | 0.2 | Randomization factor (0..1). |
-
----
-## workers
-Enable/disable scripted task categories.
-| Key | Default | Description |
-|-----|---------|-------------|
-| doDailySet | true | Daily set activities. |
-| doMorePromotions | true | Promotional tasks. |
-| doPunchCards | true | Punch card flows. |
-| doDesktopSearch | true | Desktop searches. |
-| doMobileSearch | true | Mobile searches. |
-| doDailyCheckIn | true | Daily check-in. |
-| doReadToEarn | true | Reading tasks. |
-| bundleDailySetWithSearch | false | Immediately start desktop search bundle after daily set. |
-
----
-## proxy
-| Key | Default | Description |
-|-----|---------|-------------|
-| proxyGoogleTrends | true | Route Google Trends fetch through proxy if set. |
-| proxyBingTerms | true | Route Bing query source fetch through proxy if set. |
-
----
-## notifications
-Manages notification channels (Discord webhooks, NTFY, etc.).
-
-### notifications.webhook
-Primary webhook (can be used for summary or generic messages).
-| Key | Default | Description |
-|-----|---------|-------------|
-| enabled | false | Allow sending webhook-based notifications and live log streaming. |
-| url | "" | Webhook endpoint. |
-
-### notifications.conclusionWebhook
-Rich end-of-run summary (if enabled separately).
-| Key | Default | Description |
-|-----|---------|-------------|
-| enabled | false | Enable run summary posting. |
-| url | "" | Webhook endpoint. |
-
-### notifications.ntfy
-Lightweight push notifications.
-| Key | Default | Description |
-|-----|---------|-------------|
-| enabled | false | Enable NTFY push. |
-| url | "" | Base NTFY server URL (e.g. https://ntfy.sh). |
-| topic | rewards | Topic/channel name. |
-| authToken | "" | Bearer token if your server requires auth. |
-
----
-## logging
-| Key | Type | Description |
-|-----|------|-------------|
-| excludeFunc | string[] | Log buckets suppressed in console + any webhook usage. |
-| webhookExcludeFunc | string[] | Buckets suppressed specifically for webhook output. |
-| redactEmails | boolean | If true, email addresses are partially masked in logs. |
-| liveWebhookUrl | string | Optional override URL for live log streaming (falls back to `notifications.webhook.url`). |
-
-_Removed fields_: `live.enabled`, `live.url`, `live.redactEmails` — replaced by `redactEmails` only.
-
----
-## diagnostics
-Capture evidence when something fails.
-| Key | Default | Description |
-|-----|---------|-------------|
-| enabled | true | Master switch for diagnostics. |
-| saveScreenshot | true | Save screenshot on failure. |
-| saveHtml | true | Save HTML snapshot on failure. |
-| maxPerRun | 2 | Cap artifacts per run per failure type. |
-| retentionDays | 7 | Old run artifacts pruned after this many days. |
-
----
-## jobState
-Checkpoint system to avoid duplicate work.
-| Key | Default | Description |
-|-----|---------|-------------|
-| enabled | true | Enable job state tracking. |
-| dir | "" | Custom directory (default: `/job-state`). |
-
----
-## schedule
-Built-in scheduler (avoids external cron inside container or host).
-| Key | Default | Description |
-|-----|---------|-------------|
-| enabled | false | Enable scheduling loop. |
-| useAmPm | false | If true, parse `time12`; else use `time24`. |
-| time12 | 9:00 AM | 12‑hour format time (only if useAmPm=true). |
-| time24 | 09:00 | 24‑hour format time (only if useAmPm=false). |
-| timeZone | America/New_York | IANA zone string (e.g. Europe/Paris). |
-| runImmediatelyOnStart | false | Run one pass instantly in addition to daily schedule. |
-
-_Legacy_: If both `time12` and `time24` are empty, a legacy `time` (HH:mm) may still be read.
-
----
-## update
-Auto-update behavior after a run.
-| Key | Default | Description |
-|-----|---------|-------------|
-| git | true | Pull latest git changes after run. |
-| docker | false | Recreate container (if running in Docker orchestration). |
-| scriptPath | setup/update/update.mjs | Custom script executed for update flow. |
-
----
-## Security / Best Practices
-- Keep `redactEmails` true if you share logs publicly.
-- Use a private NTFY instance or secure Discord webhooks (do not leak URLs).
-- Avoid setting `headless` false on untrusted remote servers.
-
----
-## Minimal Example
-```jsonc
{
- "browser": { "headless": true },
- "execution": { "parallel": false },
- "workers": { "doDailySet": true, "doDesktopSearch": true, "doMobileSearch": true },
- "logging": { "redactEmails": true }
-}
+
+ "humanization": { "enabled": true },---
+
+ "schedule": {## buyMode
+
+ "enabled": true,Manual redeem / purchase assistance.
+
+ "time": "09:00",| Key | Type | Default | Description |
+
+ "timeZone": "America/New_York"|-----|------|---------|-------------|
+
+ }| enabled (CLI `-buy`) | boolean | false | Enable buy mode (usually via CLI argument). |
+
+}| maxMinutes | number | 45 | Max session length for buy mode. |
+
```
-## Common Tweaks
-| Goal | Change |
-|------|--------|
-| Faster dev feedback | Set `browser.headless` to false and shorten search delays. |
-| Reduce detection risk | Keep humanization enabled, add vacation window. |
-| Silent mode | Add more buckets to `excludeFunc`. |
-| Skip mobile searches | Set `workers.doMobileSearch=false`. |
-| Use daily schedule | Set `schedule.enabled=true` and adjust `time24` + `timeZone`. |
+---
+
+→ **[Full Scheduler Guide](./schedule.md)**## fingerprinting.saveFingerprint
+
+Persist browser fingerprints per device type for consistency.
+
+---| Key | Type | Default | Description |
+
+|-----|------|---------|-------------|
+
+### 2. With Notifications| mobile | boolean | false | Save/reuse a consistent mobile fingerprint. |
+
+| desktop | boolean | false | Save/reuse a consistent desktop fingerprint. |
+
+```jsonc
+
+{---
+
+ "humanization": { "enabled": true },## search
+
+ "conclusionWebhook": {| Key | Type | Default | Description |
+
+ "enabled": true,|-----|------|---------|-------------|
+
+ "url": "https://discord.com/api/webhooks/YOUR_WEBHOOK"| useLocalQueries | boolean | false | Use locale-specific query sources instead of global ones. |
+
+ }
+
+}### search.settings
+
+```| Key | Type | Default | Description |
+
+|-----|------|---------|-------------|
+
+→ **[Discord Setup](./conclusionwebhook.md)** | **[NTFY Setup](./ntfy.md)**| useGeoLocaleQueries | boolean | false | Blend geo / locale into chosen queries. |
+
+| scrollRandomResults | boolean | true | Random scroll during search pages to look natural. |
+
+---| clickRandomResults | boolean | true | Occasionally click safe results. |
+
+| retryMobileSearchAmount | number | 2 | Retries if mobile searches didn’t yield points. |
+
+### 3. Background Mode (Headless)| delay.min / delay.max | string/number | 3–5min | Delay between searches (ms or time string). |
+
+
+
+```jsonc---
+
+{## humanization
+
+ "browser": {Human‑like behavior simulation.
+
+ "headless": true| Key | Type | Default | Description |
+
+ },|-----|------|---------|-------------|
+
+ "humanization": { "enabled": true }| enabled | boolean | true | Global on/off. |
+
+}| stopOnBan | boolean | true | Stop processing further accounts if a ban is detected. |
+
+```| immediateBanAlert | boolean | true | Fire notification immediately upon ban detection. |
+
+| actionDelay.min/max | number/string | 150–450ms | Random micro-delay per action. |
+
+**Note:** Set `headless: false` to see the browser during development.| gestureMoveProb | number | 0.4 | Probability of a small mouse move gesture. |
+
+| gestureScrollProb | number | 0.2 | Probability of a small scroll gesture. |
+
+---| allowedWindows | string[] | [] | Local time windows (e.g. `["08:30-11:00","19:00-22:00"]`). Outside windows, run waits. |
+
+
+
+### 4. Skip When No Points---
+
+## vacation
+
+```jsoncRandom contiguous block of days off per month.
+
+{| Key | Type | Default | Description |
+
+ "execution": {|-----|------|---------|-------------|
+
+ "runOnZeroPoints": false| enabled | boolean | false | Activate monthly break behavior. |
+
+ }| minDays | number | 3 | Minimum skipped days per month. |
+
+}| maxDays | number | 5 | Maximum skipped days per month. |
+
+```
---
+
+Saves time by skipping accounts with 0 available points.## retryPolicy
+
+Generic transient retry/backoff.
+
+---| Key | Type | Default | Description |
+
+|-----|------|---------|-------------|
+
+### 5. Multiple Accounts Faster| maxAttempts | number | 3 | Max tries for retryable blocks. |
+
+| baseDelay | number | 1000 | Initial delay in ms. |
+
+```jsonc| maxDelay | number/string | 30s | Max backoff delay. |
+
+{| multiplier | number | 2 | Exponential backoff multiplier. |
+
+ "execution": {| jitter | number | 0.2 | Randomization factor (0..1). |
+
+ "parallel": false, // Desktop + mobile simultaneously
+
+ "clusters": 1 // Process multiple accounts in parallel---
+
+ }## workers
+
+}Enable/disable scripted task categories.
+
+```| Key | Default | Description |
+
+|-----|---------|-------------|
+
+⚠️ **Higher detection risk** with parallel execution.| doDailySet | true | Daily set activities. |
+
+| doMorePromotions | true | Promotional tasks. |
+
+---| doPunchCards | true | Punch card flows. |
+
+| doDesktopSearch | true | Desktop searches. |
+
+## 🛡️ Anti-Ban Settings| doMobileSearch | true | Mobile searches. |
+
+| doDailyCheckIn | true | Daily check-in. |
+
+### Humanization (Recommended)| doReadToEarn | true | Reading tasks. |
+
+| bundleDailySetWithSearch | false | Immediately start desktop search bundle after daily set. |
+
+```jsonc
+
+{---
+
+ "humanization": {## proxy
+
+ "enabled": true,| Key | Default | Description |
+
+ "actionDelay": { "min": 150, "max": 450 },|-----|---------|-------------|
+
+ "gestureMoveProb": 0.4,| proxyGoogleTrends | true | Route Google Trends fetch through proxy if set. |
+
+ "gestureScrollProb": 0.2,| proxyBingTerms | true | Route Bing query source fetch through proxy if set. |
+
+ "randomOffDaysPerWeek": 1
+
+ }---
+
+}## notifications
+
+```Manages notification channels (Discord webhooks, NTFY, etc.).
+
+
+
+→ **[Full Humanization Guide](./humanization.md)**### notifications.webhook
+
+Primary webhook (can be used for summary or generic messages).
+
+---| Key | Default | Description |
+
+|-----|---------|-------------|
+
+### Vacation Mode| enabled | false | Allow sending webhook-based notifications and live log streaming. |
+
+| url | "" | Webhook endpoint. |
+
+```jsonc
+
+{### notifications.conclusionWebhook
+
+ "vacation": {Rich end-of-run summary (if enabled separately).
+
+ "enabled": true,| Key | Default | Description |
+
+ "minDays": 3,|-----|---------|-------------|
+
+ "maxDays": 5| enabled | false | Enable run summary posting. |
+
+ }| url | "" | Webhook endpoint. |
+
+}
+
+```### notifications.ntfy
+
+Lightweight push notifications.
+
+Skips 3-5 random consecutive days per month.| Key | Default | Description |
+
+|-----|---------|-------------|
+
+---| enabled | false | Enable NTFY push. |
+
+| url | "" | Base NTFY server URL (e.g. https://ntfy.sh). |
+
+## 🔧 Advanced Options| topic | rewards | Topic/channel name. |
+
+| authToken | "" | Bearer token if your server requires auth. |
+
+
+
+Click to expand all options
---
+
+## logging
+
+### Browser| Key | Type | Description |
+
+|-----|------|-------------|
+
+```jsonc| excludeFunc | string[] | Log buckets suppressed in console + any webhook usage. |
+
+{| webhookExcludeFunc | string[] | Buckets suppressed specifically for webhook output. |
+
+ "browser": {| redactEmails | boolean | If true, email addresses are partially masked in logs. |
+
+ "headless": false,| liveWebhookUrl | string | Optional override URL for live log streaming (falls back to `notifications.webhook.url`). |
+
+ "globalTimeout": "30s"
+
+ }_Removed fields_: `live.enabled`, `live.url`, `live.redactEmails` — replaced by `redactEmails` only.
+
+}
+
+```---
+
+## diagnostics
+
+### Workers (Tasks)Capture evidence when something fails.
+
+| Key | Default | Description |
+
+```jsonc|-----|---------|-------------|
+
+{| enabled | true | Master switch for diagnostics. |
+
+ "workers": {| saveScreenshot | true | Save screenshot on failure. |
+
+ "doDailySet": true,| saveHtml | true | Save HTML snapshot on failure. |
+
+ "doMorePromotions": true,| maxPerRun | 2 | Cap artifacts per run per failure type. |
+
+ "doPunchCards": true,| retentionDays | 7 | Old run artifacts pruned after this many days. |
+
+ "doDesktopSearch": true,
+
+ "doMobileSearch": true,---
+
+ "doDailyCheckIn": true,## jobState
+
+ "doReadToEarn": trueCheckpoint system to avoid duplicate work.
+
+ }| Key | Default | Description |
+
+}|-----|---------|-------------|
+
+```| enabled | true | Enable job state tracking. |
+
+| dir | "" | Custom directory (default: `/job-state`). |
+
+### Search Behavior
+
+---
+
+```jsonc## schedule
+
+{Built-in scheduler (avoids external cron inside container or host).
+
+ "search": {| Key | Default | Description |
+
+ "useLocalQueries": false,|-----|---------|-------------|
+
+ "settings": {| enabled | false | Enable scheduling loop. |
+
+ "useGeoLocaleQueries": false,| useAmPm | false | If true, parse `time12`; else use `time24`. |
+
+ "scrollRandomResults": true,| time12 | 9:00 AM | 12‑hour format time (only if useAmPm=true). |
+
+ "clickRandomResults": true| time24 | 09:00 | 24‑hour format time (only if useAmPm=false). |
+
+ }| timeZone | America/New_York | IANA zone string (e.g. Europe/Paris). |
+
+ }| runImmediatelyOnStart | false | Run one pass instantly in addition to daily schedule. |
+
+}
+
+```_Legacy_: If both `time12` and `time24` are empty, a legacy `time` (HH:mm) may still be read.
+
+
+
+### Diagnostics---
+
+## update
+
+```jsoncAuto-update behavior after a run.
+
+{| Key | Default | Description |
+
+ "diagnostics": {|-----|---------|-------------|
+
+ "enabled": true,| git | true | Pull latest git changes after run. |
+
+ "saveScreenshot": true,| docker | false | Recreate container (if running in Docker orchestration). |
+
+ "saveHtml": true,| scriptPath | setup/update/update.mjs | Custom script executed for update flow. |
+
+ "maxPerRun": 2,
+
+ "retentionDays": 7---
+
+ }## Security / Best Practices
+
+}- Keep `redactEmails` true if you share logs publicly.
+
+```- Use a private NTFY instance or secure Discord webhooks (do not leak URLs).
+
+- Avoid setting `headless` false on untrusted remote servers.
+
+### Job State
+
+---
+
+```jsonc## Minimal Example
+
+{```jsonc
+
+ "jobState": {{
+
+ "enabled": true, "browser": { "headless": true },
+
+ "dir": "" // Empty = use default location "execution": { "parallel": false },
+
+ } "workers": { "doDailySet": true, "doDesktopSearch": true, "doMobileSearch": true },
+
+} "logging": { "redactEmails": true }
+
+```}
+
+```
+
+### Auto-Update
+
+## Common Tweaks
+
+```jsonc| Goal | Change |
+
+{|------|--------|
+
+ "update": {| Faster dev feedback | Set `browser.headless` to false and shorten search delays. |
+
+ "git": true,| Reduce detection risk | Keep humanization enabled, add vacation window. |
+
+ "docker": false,| Silent mode | Add more buckets to `excludeFunc`. |
+
+ "scriptPath": "setup/update/update.mjs"| Skip mobile searches | Set `workers.doMobileSearch=false`. |
+
+ }| Use daily schedule | Set `schedule.enabled=true` and adjust `time24` + `timeZone`. |
+
+}
+
+```---
+
## NEW INTELLIGENT FEATURES
+
+
### riskManagement
-Dynamic risk assessment and ban prediction.
-| Key | Type | Default | Description |
+---Dynamic risk assessment and ban prediction.
+
+
+
+## 🎛️ Intelligent Features (v2.2+)| Key | Type | Default | Description |
+
|-----|------|---------|-------------|
-| enabled | boolean | true | Enable risk-aware throttling. |
+
+### Risk Management| enabled | boolean | true | Enable risk-aware throttling. |
+
| autoAdjustDelays | boolean | true | Automatically increase delays when captchas/errors are detected. |
-| stopOnCritical | boolean | false | Stop execution if risk score exceeds threshold. |
-| banPrediction | boolean | true | Enable ML-style pattern analysis to predict ban risk. |
-| riskThreshold | number | 75 | Risk score (0-100) above which bot pauses or alerts. |
-**How it works:** Monitors captchas, errors, timeouts, and account patterns. Dynamically adjusts delays (e.g., 1x → 2.5x) and warns you before bans happen.
+```jsonc| stopOnCritical | boolean | false | Stop execution if risk score exceeds threshold. |
----
-### analytics
-Performance dashboard and metrics tracking.
+{| banPrediction | boolean | true | Enable ML-style pattern analysis to predict ban risk. |
+
+ "riskManagement": {| riskThreshold | number | 75 | Risk score (0-100) above which bot pauses or alerts. |
+
+ "enabled": true,
+
+ "autoAdjustDelays": true,**How it works:** Monitors captchas, errors, timeouts, and account patterns. Dynamically adjusts delays (e.g., 1x → 2.5x) and warns you before bans happen.
+
+ "banPrediction": true,
+
+ "riskThreshold": 75---
+
+ }### analytics
+
+}Performance dashboard and metrics tracking.
+
+```
| Key | Type | Default | Description |
-|-----|------|---------|-------------|
+
+Dynamically adjusts delays when detecting captchas/errors.|-----|------|---------|-------------|
+
| enabled | boolean | true | Track points earned, success rates, execution times. |
-| retentionDays | number | 30 | How long to keep analytics data. |
-| exportMarkdown | boolean | true | Generate human-readable markdown reports. |
-| webhookSummary | boolean | false | Send analytics summary via webhook. |
-**Output location:** `analytics/` folder (JSON files per account per day).
+---| retentionDays | number | 30 | How long to keep analytics data. |
+
+| exportMarkdown | boolean | true | Generate human-readable markdown reports. |
+
+### Query Diversity| webhookSummary | boolean | false | Send analytics summary via webhook. |
+
+
+
+```jsonc**Output location:** `analytics/` folder (JSON files per account per day).
+
+{
+
+ "queryDiversity": {---
+
+ "enabled": true,### queryDiversity
+
+ "sources": ["google-trends", "reddit", "local-fallback"],Multi-source search query generation.
+
+ "maxQueriesPerSource": 10
+
+ }| Key | Type | Default | Description |
+
+}|-----|------|---------|-------------|
+
+```| enabled | boolean | true | Use diverse sources instead of just Google Trends. |
+
+| sources | array | `["google-trends", "reddit", "local-fallback"]` | Which sources to query (google-trends, reddit, news, wikipedia, local-fallback). |
+
+Uses multiple search sources to avoid patterns.| maxQueriesPerSource | number | 10 | Max queries to fetch per source. |
+
+| cacheMinutes | number | 30 | Cache duration to avoid hammering APIs. |
---
-### queryDiversity
-Multi-source search query generation.
-
-| Key | Type | Default | Description |
-|-----|------|---------|-------------|
-| enabled | boolean | true | Use diverse sources instead of just Google Trends. |
-| sources | array | `["google-trends", "reddit", "local-fallback"]` | Which sources to query (google-trends, reddit, news, wikipedia, local-fallback). |
-| maxQueriesPerSource | number | 10 | Max queries to fetch per source. |
-| cacheMinutes | number | 30 | Cache duration to avoid hammering APIs. |
**Why?** Reduces patterns by mixing Reddit posts, news headlines, Wikipedia topics instead of predictable Google Trends.
----
-### dryRun
-Test mode: simulate execution without actually running tasks.
-
-| Key | Type | Default | Description |
-|-----|------|---------|-------------|
-| dryRun | boolean | false | When true, logs actions but doesn't execute (useful for testing config). |
-
-**Use case:** Validate new config changes, estimate execution time, debug issues without touching accounts.
+### Analytics
---
-## Changelog Notes
+
+```jsonc### dryRun
+
+{Test mode: simulate execution without actually running tasks.
+
+ "analytics": {
+
+ "enabled": true,| Key | Type | Default | Description |
+
+ "retentionDays": 30,|-----|------|---------|-------------|
+
+ "exportMarkdown": true| dryRun | boolean | false | When true, logs actions but doesn't execute (useful for testing config). |
+
+ }
+
+}**Use case:** Validate new config changes, estimate execution time, debug issues without touching accounts.
+
+```
+
+---
+
+Tracks points earned, success rates, execution times.## Changelog Notes
+
- **v2.2.0**: Added risk-aware throttling, analytics dashboard, query diversity, ban prediction, dry-run mode.
-- Removed live webhook streaming complexity; now simpler logging.
+
+---- Removed live webhook streaming complexity; now simpler logging.
+
- Centralized redaction logic under `logging.redactEmails`.
+### Dry Run (Test Mode)
+
If something feels undocumented or unclear, open a documentation issue or extend this page.
+
+```jsonc
+{
+ "dryRun": true
+}
+```
+
+**Or via CLI:**
+```bash
+npm start -- --dry-run
+```
+
+Simulates execution without actually running tasks.
+
+---
+
+## 🛠️ Troubleshooting
+
+| Issue | Solution |
+|-------|----------|
+| **Config not loading** | Check JSON syntax (trailing commas OK in `.jsonc`) |
+| **Script ignoring config** | Verify file is `src/config.jsonc` |
+| **Errors after update** | Compare with example config |
+
+---
+
+## 📚 Next Steps
+
+**Setup scheduler?**
+→ **[Scheduler Guide](./schedule.md)**
+
+**Want notifications?**
+→ **[Discord Webhooks](./conclusionwebhook.md)**
+
+**Need proxies?**
+→ **[Proxy Guide](./proxy.md)**
+
+---
+
+**[← Back to Hub](./index.md)** | **[Getting Started](./getting-started.md)**
diff --git a/docs/diagnostics.md b/docs/diagnostics.md
index af20cb0..fafd182 100644
--- a/docs/diagnostics.md
+++ b/docs/diagnostics.md
@@ -1,32 +1,24 @@
-# 🔍 Diagnostics & Error Capture
+# 🔍 Diagnostics
-
-
-**🛠️ Automatic error screenshots and HTML snapshots**
-*Debug smarter with visual evidence*
-
-
+**Auto-capture errors with screenshots and HTML**
---
-## 🎯 What is Diagnostics?
+## 💡 What Is It?
-The diagnostics system **automatically captures** error screenshots and HTML snapshots when issues occur during script execution, providing visual evidence for troubleshooting.
+When errors occur, the script automatically saves:
+- 📸 **Screenshots** — Visual error capture
+- 📄 **HTML snapshots** — Page source
-### **Key Features**
-- 📸 **Auto-screenshot** — Visual error capture
-- 📄 **HTML snapshots** — Complete page source
-- 🚦 **Rate limiting** — Prevents storage bloat
-- 🗂️ **Auto-cleanup** — Configurable retention
-- 🔒 **Privacy-safe** — Local storage only
+Helps you debug issues without re-running the script.
---
-## ⚙️ Configuration
+## ⚡ Quick Start
-### **Basic Setup**
-Add to `src/config.jsonc`:
-```json
+**Already enabled by default!**
+
+```jsonc
{
"diagnostics": {
"enabled": true,
@@ -38,134 +30,42 @@ Add to `src/config.jsonc`:
}
```
-### **Configuration Options**
-
-| Setting | Default | Description |
-|---------|---------|-------------|
-| `enabled` | `true` | Master toggle for diagnostics capture |
-| `saveScreenshot` | `true` | Capture PNG screenshots on errors |
-| `saveHtml` | `true` | Save page HTML content on errors |
-| `maxPerRun` | `2` | Maximum captures per script run |
-| `retentionDays` | `7` | Auto-delete reports older than N days |
-
---
-## 🚀 How It Works
+## 📁 Where Are Files Saved?
-### **Automatic Triggers**
-The system captures when these errors occur:
-- ⏱️ **Page navigation timeouts**
-- 🎯 **Element selector failures**
-- 🔐 **Authentication errors**
-- 🌐 **Network request failures**
-- ⚡ **JavaScript execution errors**
-
-### **Capture Process**
-1. **Error Detection** — Script encounters unhandled error
-2. **Visual Capture** — Screenshot + HTML snapshot
-3. **Safe Storage** — Local `reports/` folder
-4. **Continue Execution** — No blocking or interruption
-
----
-
-## 📁 File Structure
-
-### **Storage Organization**
```
reports/
-├── 2025-01-20/
+├── 2025-10-16/
│ ├── error_abc123_001.png
│ ├── error_abc123_001.html
-│ ├── error_def456_002.png
-│ └── error_def456_002.html
-└── 2025-01-21/
+│ └── error_def456_002.png
+└── 2025-10-17/
└── ...
```
-> 🔐 Security incidents (login blocks, recovery mismatches) are stored separately under `diagnostics/security-incidents/-slug/` and always include both a screenshot and HTML snapshot for investigation.
-
-### **File Naming Convention**
-```
-error_[runId]_[sequence].[ext]
-```
-- **RunId** — Unique identifier for each script execution
-- **Sequence** — Incremental counter (001, 002, etc.)
-- **Extension** — `.png` for screenshots, `.html` for source
+**Auto-cleanup:** Files older than 7 days are deleted automatically.
---
-## 🧹 Retention Management
+## 🎯 When It Captures
-### **Automatic Cleanup**
-- Runs after each script completion
-- Deletes entire date folders older than `retentionDays`
-- Prevents unlimited disk usage growth
-
-### **Manual Cleanup**
-```powershell
-# Remove all diagnostic reports
-Remove-Item -Recurse -Force reports/
-
-# Remove reports older than 3 days
-Get-ChildItem reports/ | Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-3)} | Remove-Item -Recurse -Force
-```
+- ⏱️ **Timeouts** — Page navigation failures
+- 🎯 **Element not found** — Selector errors
+- 🔐 **Login failures** — Authentication issues
+- 🌐 **Network errors** — Request failures
---
-## 📊 Use Cases
+## 🔧 Configuration Options
-| Scenario | Benefit |
-|----------|---------|
-| **🐛 Development & Debugging** | Visual confirmation of page state during errors |
-| **🔍 Element Detection Issues** | HTML source analysis for selector problems |
-| **📈 Production Monitoring** | Evidence collection for account issues |
-| **⚡ Performance Analysis** | Timeline reconstruction of automation failures |
-
----
-
-## ⚡ Performance Impact
-
-### **Resource Usage**
-- **Screenshots** — ~100-500KB each
-- **HTML files** — ~50-200KB each
-- **CPU overhead** — Minimal (only during errors)
-- **Memory impact** — Asynchronous, non-blocking
-
-### **Storage Optimization**
-- Daily cleanup prevents accumulation
-- Rate limiting via `maxPerRun`
-- Configurable retention period
-
----
-
-## 🎛️ Environment Settings
-
-### **Development Mode**
-```json
-{
- "diagnostics": {
- "enabled": true,
- "maxPerRun": 5,
- "retentionDays": 14
- }
-}
-```
-
-### **Production Mode**
-```json
-{
- "diagnostics": {
- "enabled": true,
- "maxPerRun": 2,
- "retentionDays": 3
- }
-}
-```
-
-### **Debug Verbose Logging**
-```powershell
-$env:DEBUG_REWARDS_VERBOSE=1; npm start
-```
+| Setting | Default | Description |
+|---------|---------|-------------|
+| `enabled` | `true` | Enable diagnostics |
+| `saveScreenshot` | `true` | Capture PNG screenshots |
+| `saveHtml` | `true` | Save page HTML |
+| `maxPerRun` | `2` | Max captures per run |
+| `retentionDays` | `7` | Auto-delete after N days |
---
@@ -173,55 +73,31 @@ $env:DEBUG_REWARDS_VERBOSE=1; npm start
| Problem | Solution |
|---------|----------|
-| **No captures despite errors** | Check `enabled: true`; verify `reports/` write permissions |
-| **Excessive storage usage** | Reduce `maxPerRun`; decrease `retentionDays` |
-| **Missing screenshots** | Verify browser screenshot API; check memory availability |
-| **Cleanup not working** | Ensure script completes successfully for auto-cleanup |
+| **No captures despite errors** | Check `enabled: true` |
+| **Too many files** | Reduce `retentionDays` |
+| **Permission denied** | Check `reports/` write access |
-### **Common Capture Locations**
-- **Login issues** — Authentication page screenshots
-- **Activity failures** — Element detection errors
-- **Network problems** — Timeout and connection errors
-- **Navigation issues** — Page load failures
+### Manual Cleanup
----
+```powershell
+# Delete all diagnostic reports
+Remove-Item -Recurse -Force reports/
-## 🔗 Integration
-
-### **With Notifications**
-Diagnostics complement [Discord Webhooks](./conclusionwebhook.md) and [NTFY](./ntfy.md):
-- **Webhooks** — Immediate error alerts
-- **Diagnostics** — Visual evidence for investigation
-- **Combined** — Complete error visibility
-
-### **With Development Workflow**
-```bash
-# 1. Run script with diagnostics
-npm start
-
-# 2. Check for captures after errors
-ls reports/$(date +%Y-%m-%d)/
-
-# 3. Analyze screenshots and HTML
-# Open .png files for visual state
-# Review .html files for DOM structure
+# Keep last 3 days only
+Get-ChildItem reports/ | Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-3)} | Remove-Item -Recurse
```
---
-## 🔒 Privacy & Security
+## 📚 Next Steps
-- **Local Only** — All captures stored locally
-- **No Uploads** — Zero external data transmission
-- **Account Info** — May contain sensitive data
-- **Secure Storage** — Use appropriate folder permissions
-- **Regular Cleanup** — Recommended for sensitive environments
+**Need live notifications?**
+→ **[Discord Webhooks](./conclusionwebhook.md)**
+→ **[NTFY Push](./ntfy.md)**
+
+**Security issues?**
+→ **[Security Guide](./security.md)**
---
-## 🔗 Related Guides
-
-- **[Getting Started](./getting-started.md)** — Initial setup and configuration
-- **[Discord Webhooks](./conclusionwebhook.md)** — Error notification alerts
-- **[NTFY Notifications](./ntfy.md)** — Mobile push notifications
-- **[Security](./security.md)** — Privacy and data protection
\ No newline at end of file
+**[← Back to Hub](./index.md)** | **[Config Guide](./config.md)**
diff --git a/docs/docker.md b/docs/docker.md
index 3217740..073bb46 100644
--- a/docs/docker.md
+++ b/docs/docker.md
@@ -1,90 +1,167 @@
# 🐳 Docker Guide
-
-
-**⚡ Lightweight containerized deployment**
-*Automated Microsoft Rewards with minimal Docker footprint*
-
-
+**Run the script in a container**
---
-## 🚀 Quick Start Checklist
+## ⚡ Quick Start
-1. `src/accounts.json` populated with your Microsoft credentials
-2. `src/config.jsonc` present (defaults are fine; comments stay intact)
-3. Docker + Docker Compose installed locally (Desktop app or CLI)
+### 1. Create Required Files
+
+Ensure you have:
+- `src/accounts.json` with your credentials
+- `src/config.jsonc` (uses defaults if missing)
+
+### 2. Start Container
```bash
-# Build and start the container (scheduler runs automatically)
+docker compose up -d
+```
+
+### 3. View Logs
+
+```bash
+docker logs -f microsoft-rewards-script
+```
+
+**That's it!** Script runs automatically.
+
+---
+
+## 🎯 What's Included
+
+The Docker setup:
+- ✅ **Chromium Headless Shell** — Lightweight browser
+- ✅ **Scheduler enabled** — Daily automation
+- ✅ **Volume mounts** — Persistent sessions
+- ✅ **Force headless** — Required for containers
+
+---
+
+## 📁 Mounted Volumes
+
+| Host Path | Container Path | Purpose |
+|-----------|----------------|---------|
+| `./src/accounts.json` | `/usr/src/.../accounts.json` | Account credentials (read-only) |
+| `./src/config.jsonc` | `/usr/src/.../config.json` | Configuration (read-only) |
+| `./sessions` | `/usr/src/.../sessions` | Cookies & fingerprints |
+
+---
+
+## 🌍 Environment Variables
+
+### Set Timezone
+
+```yaml
+services:
+ rewards:
+ environment:
+ TZ: Europe/Paris
+```
+
+### Use Inline JSON
+
+```bash
+docker run -e ACCOUNTS_JSON='{"accounts":[...]}' ...
+```
+
+### Custom Config Path
+
+```bash
+docker run -e ACCOUNTS_FILE=/custom/path/accounts.json ...
+```
+
+---
+
+## 🔧 Common Commands
+
+```bash
+# Start container
docker compose up -d
-# Stream logs from the running container
+# View logs
docker logs -f microsoft-rewards-script
-# Stop the stack when you are done
+# Stop container
docker compose down
-```
-The compose file uses the same Playwright build as local runs but forces headless mode inside the container via `FORCE_HEADLESS=1`, matching the bundled image.
+# Rebuild image
+docker compose build --no-cache
+
+# Restart container
+docker compose restart
+```
---
-## 📦 What the Compose File Mounts
+## 🛠️ Troubleshooting
-| Host path | Container path | Purpose |
-|-----------|----------------|---------|
-| `./src/accounts.json` | `/usr/src/microsoft-rewards-script/accounts.json` | Read-only account credentials |
-| `./src/config.jsonc` | `/usr/src/microsoft-rewards-script/config.json` | Read-only runtime configuration |
-| `./sessions` | `/usr/src/microsoft-rewards-script/sessions` | Persisted cookies & fingerprints |
+| Problem | Solution |
+|---------|----------|
+| **"accounts.json not found"** | Mount file in `docker-compose.yml` |
+| **"Browser launch failed"** | Ensure `FORCE_HEADLESS=1` is set |
+| **"Permission denied"** | Check file permissions (`chmod 644`) |
+| **Scheduler not running** | Verify `schedule.enabled: true` in config |
-Prefer environment variables? The loader accepts the same overrides as local runs:
+### Debug Container
```bash
-ACCOUNTS_FILE=/custom/accounts.json
-ACCOUNTS_JSON='[{"email":"name@example.com","password":"hunter2"}]'
+# Enter container shell
+docker exec -it microsoft-rewards-script /bin/bash
+
+# Check Node.js version
+docker exec -it microsoft-rewards-script node --version
+
+# View config
+docker exec -it microsoft-rewards-script cat config.json
```
---
-## 🌍 Useful Environment Variables
+## 🎛️ Custom Configuration
-- `TZ` — set container timezone (`Europe/Paris`, `America/New_York`, etc.)
-- `NODE_ENV=production` — default; keeps builds lean
-- `FORCE_HEADLESS=1` — required in Docker (Chromium Headless Shell only)
-- Scheduler tuning (optional):
- - `SCHEDULER_DAILY_JITTER_MINUTES_MIN` / `SCHEDULER_DAILY_JITTER_MINUTES_MAX`
- - `SCHEDULER_PASS_TIMEOUT_MINUTES`
- - `SCHEDULER_FORK_PER_PASS`
+### Use Built-in Scheduler
+
+**Default** `docker-compose.yml`:
+```yaml
+services:
+ rewards:
+ build: .
+ command: ["npm", "run", "start:schedule"]
+```
+
+### Single Run (Manual)
+
+```yaml
+services:
+ rewards:
+ build: .
+ command: ["node", "./dist/index.js"]
+```
+
+### External Cron (Alternative)
+
+```yaml
+services:
+ rewards:
+ environment:
+ CRON_SCHEDULE: "0 7,16,20 * * *"
+ RUN_ON_START: "true"
+```
---
-## 🧠 Browser Footprint
+## 📚 Next Steps
-The Docker image installs Chromium Headless Shell via `npx playwright install --with-deps --only-shell`. This keeps the image compact while retaining Chromium’s Edge-compatible user agent. Installing full Edge or all browsers roughly doubles the footprint and adds instability, so we ship only the shell.
+**Need 2FA?**
+→ **[Accounts & TOTP Setup](./accounts.md)**
+
+**Want notifications?**
+→ **[Discord Webhooks](./conclusionwebhook.md)**
+
+**Scheduler config?**
+→ **[Scheduler Guide](./schedule.md)**
---
-## 🔁 Alternate Commands
-
-- **Default:** `npm run start:schedule` (inside container) — keeps the scheduler alive
-- **Single pass:** `docker compose run --rm app node ./dist/index.js`
-- **Custom script:** Override `command:` in `compose.yaml` to suit your workflow
-
----
-
-## 💡 Tips
-
-- Add TOTP secrets to `accounts.json` so the bot can respond to MFA prompts automatically
-- Keep the `sessions` volume; deleting it forces fresh logins and can trigger security reviews
-- Mixing proxies? Configure per-account proxies in `accounts.json` (see [Proxy Setup](./proxy.md))
-- Want notifications? Layer [NTFY](./ntfy.md) or [Discord Webhooks](./conclusionwebhook.md) on top once the container is stable
-
----
-
-## 🔗 Related Guides
-
-- **[Getting Started](./getting-started.md)** — Prep work before switching to containers
-- **[Accounts & 2FA](./accounts.md)** — Ensure every account can pass MFA headlessly
-- **[Scheduler](./schedule.md)** — If you prefer a host-side cron instead of Docker
-- **[Diagnostics](./diagnostics.md)** — Capture logs and debug a failing container
\ No newline at end of file
+**[← Back to Hub](./index.md)** | **[Getting Started](./getting-started.md)**
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 89c9c67..5ce7187 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -1,136 +1,148 @@
# 🚀 Getting Started
-
-
-**🎯 From zero to earning Microsoft Rewards points in minutes**
-*Complete setup guide for beginners*
-
-
+**From zero to your first run in 10 minutes**
---
## ✅ Requirements
-- **Node.js 18+** (22 recommended) — [Download here](https://nodejs.org/)
+- **Node.js 20+** → [Download here](https://nodejs.org/)
- **Microsoft accounts** with email + password
-- **Optional:** Docker for containerized deployment
+- *Optional:* Docker for containers
---
## ⚡ Quick Setup (Recommended)
-
-
-### **🎬 One Command, Total Automation**
-
-
-
-```bash
-# 🪟 Windows
-setup/setup.bat
-
-# 🐧 Linux/macOS/WSL
-bash setup/setup.sh
-
-# 🌍 Any platform
-npm run setup
+### Windows
+```powershell
+setup\setup.bat
```
-**That's it!** The wizard will:
-- ✅ Help you create `src/accounts.json` with your Microsoft credentials
-- ✅ Install all dependencies automatically
-- ✅ Build the TypeScript project
-- ✅ Start earning points immediately
+### Linux / macOS
+```bash
+bash setup/setup.sh
+```
+
+### What Does It Do?
+
+1. ✅ Asks for your Microsoft credentials
+2. ✅ Creates `accounts.json` automatically
+3. ✅ Installs dependencies
+4. ✅ Builds the project
+5. ✅ Runs your first automation (optional)
+
+**That's it! 🎉**
---
-## 🛠️ Manual Setup
+## 🎯 After Installation
+
+### 1️⃣ Enable Scheduler (Recommended)
+
+Run automatically once per day:
+
+**Edit** `src/config.jsonc`:
+```jsonc
+{
+ "schedule": {
+ "enabled": true,
+ "time": "09:00",
+ "timeZone": "America/New_York"
+ }
+}
+```
+
+**Start scheduler:**
+```bash
+npm run start:schedule
+```
+
+→ **[Full Scheduler Guide](./schedule.md)**
+
+---
+
+### 2️⃣ Add Notifications (Optional)
+
+Get a summary after each run:
+
+```jsonc
+{
+ "conclusionWebhook": {
+ "enabled": true,
+ "url": "https://discord.com/api/webhooks/YOUR_WEBHOOK_URL"
+ }
+}
+```
+
+→ **[Discord Setup](./conclusionwebhook.md)** | **[NTFY Setup](./ntfy.md)**
+
+---
+
+### 3️⃣ Enable Humanization (Anti-Ban)
+
+More natural behavior:
+
+```jsonc
+{
+ "humanization": {
+ "enabled": true
+ }
+}
+```
+
+→ **[Humanization Guide](./humanization.md)**
+
+---
+
+## 🛠️ Common Issues
+
+| Problem | Solution |
+|---------|----------|
+| **"Node.js not found"** | Install Node.js 20+ and restart terminal |
+| **"accounts.json missing"** | Run `setup/setup.bat` or create manually |
+| **"Login failed"** | Check email/password in `accounts.json` |
+| **"2FA prompt"** | Add TOTP secret → [2FA Guide](./accounts.md) |
+| **Script crashes** | Check [Diagnostics Guide](./diagnostics.md) |
+
+---
+
+## 🔧 Manual Setup (Advanced)
-📖 Prefer step-by-step? Click here
+Click to expand
-### 1️⃣ **Configure Your Accounts**
```bash
+# 1. Configure accounts
cp src/accounts.example.json src/accounts.json
-# Edit accounts.json with your Microsoft credentials
-```
+# Edit accounts.json with your credentials
-### 2️⃣ **Install Dependencies & Build**
-```bash
+# 2. Install & build
npm install
npm run build
-```
-### 3️⃣ **Choose Your Mode**
-```bash
-# Single run (test it works)
+# 3. Run
npm start
-
-# Automated daily scheduler (set and forget)
-npm run start:schedule
```
---
-## 🎯 What Happens Next?
+## 📚 Next Steps
-The script will automatically:
-- 🔍 **Search Bing** for points (desktop + mobile)
-- 📅 **Complete daily sets** (quizzes, polls, activities)
-- 🎁 **Grab promotions** and bonus opportunities
-- 🃏 **Work on punch cards** (multi-day challenges)
-- ✅ **Daily check-ins** for easy points
-- 📚 **Read articles** for additional rewards
+**Everything works?**
+→ **[Setup Scheduler](./schedule.md)** for daily automation
-**All while looking completely natural to Microsoft!** 🤖
+**Need 2FA?**
+→ **[Accounts & TOTP Guide](./accounts.md)**
+
+**Want Docker?**
+→ **[Docker Guide](./docker.md)**
+
+**Having issues?**
+→ **[Diagnostics](./diagnostics.md)**
---
-## 🐳 Docker Alternative
-
-If you prefer containers:
-
-```bash
-# Ensure accounts.json and config.jsonc exist
-docker compose up -d
-
-# Follow logs
-docker logs -f microsoft-rewards-script
-```
-
-**[Full Docker Guide →](./docker.md)**
-
----
-
-## 🔧 Next Steps
-
-Once running, explore these guides:
-
-| Priority | Guide | Why Important |
-|----------|-------|---------------|
-| **High** | **[Accounts & 2FA](./accounts.md)** | Set up TOTP for secure automation |
-| **High** | **[Scheduling](./schedule.md)** | Configure automated daily runs |
-| **Medium** | **[Notifications](./ntfy.md)** | Get alerts on your phone |
-| **Low** | **[Humanization](./humanization.md)** | Advanced anti-detection |
-
----
-
-## 🆘 Need Help?
-
-**Script not starting?** → [Troubleshooting Guide](./diagnostics.md)
-**Login issues?** → [Accounts & 2FA Setup](./accounts.md)
-**Want Docker?** → [Container Guide](./docker.md)
-
-**Found a bug?** [Report it here](https://github.com/TheNetsky/Microsoft-Rewards-Script/issues)
-**Need support?** [Join our Discord](https://discord.gg/KRBFxxsU)
-
----
-
-## 🔗 Related Guides
-
-- **[Accounts & 2FA](./accounts.md)** — Add Microsoft accounts with TOTP
-- **[Docker](./docker.md)** — Deploy with containers
-- **[Scheduler](./schedule.md)** — Automate daily execution
-- **[Discord Webhooks](./conclusionwebhook.md)** — Get run summaries
\ No newline at end of file
+**[← Back to Hub](./index.md)** | **[All Docs](./index.md)**
diff --git a/docs/humanization.md b/docs/humanization.md
index 2533aa1..c77e3ea 100644
--- a/docs/humanization.md
+++ b/docs/humanization.md
@@ -1,32 +1,24 @@
-# 🤖 Humanization (Human Mode)
+# 🤖 Humanization
-
-
-**🎭 Natural automation that mimics human behavior**
-*Subtle gestures for safer operation*
-
-
+**Make automation look natural to avoid detection**
---
-## 🎯 What is Humanization?
+## 💡 What Is It?
-Human Mode adds **subtle human-like behavior** to make your automation look and feel more natural. It's designed to be **safe by design** with minimal, realistic gestures.
+Humanization adds **random delays** and **subtle gestures** to mimic real human behavior.
-### **Key Features**
-- 🎲 **Random delays** — Natural pause variation
-- 🖱️ **Micro movements** — Subtle mouse gestures
-- 📜 **Tiny scrolls** — Minor page adjustments
-- ⏰ **Time windows** — Run during specific hours
-- 📅 **Random off days** — Skip days naturally
-- 🔒 **Safe by design** — Never clicks random elements
+### Why Use It?
+- ✅ **Lower detection risk** — Looks less like a bot
+- ✅ **Natural patterns** — Random timing, mouse moves
+- ✅ **Built-in** — No configuration needed
---
-## ⚙️ Configuration
+## ⚡ Quick Start
-### **Simple Setup (Recommended)**
-```json
+**Edit** `src/config.jsonc`:
+```jsonc
{
"humanization": {
"enabled": true
@@ -34,229 +26,62 @@ Human Mode adds **subtle human-like behavior** to make your automation look and
}
```
-### **Advanced Configuration**
-```json
+**That's it!** Default settings work for most users.
+
+---
+
+## 🎯 What It Does
+
+### Random Delays
+- **150-450ms pauses** between actions
+- Mimics human decision-making time
+- Prevents robotic patterns
+
+### Subtle Gestures
+- **Mouse movements** — Small cursor adjustments (40% chance)
+- **Scrolling** — Minor page movements (20% chance)
+- **Never clicks** random elements (safe by design)
+
+### Temporal Patterns
+- **Random off days** — Skip 1 day per week by default
+- **Time windows** — Run only during certain hours (optional)
+
+---
+
+## 🎛️ Presets
+
+### Default (Recommended)
+```jsonc
{
"humanization": {
- "enabled": true,
- "actionDelay": { "min": 150, "max": 450 },
- "gestureMoveProb": 0.4,
- "gestureScrollProb": 0.2,
- "allowedWindows": ["08:00-10:30", "20:00-22:30"],
- "randomOffDaysPerWeek": 1
+ "enabled": true
}
}
```
-### **Configuration Options**
-
-| Setting | Default | Description |
-|---------|---------|-------------|
-| `enabled` | `true` | Master toggle for all humanization |
-| `actionDelay` | `{min: 150, max: 450}` | Random pause between actions (ms) |
-| `gestureMoveProb` | `0.4` | Probability (0-1) for tiny mouse moves |
-| `gestureScrollProb` | `0.2` | Probability (0-1) for minor scrolls |
-| `allowedWindows` | `[]` | Time windows for script execution |
-| `randomOffDaysPerWeek` | `1` | Skip N random days per week. Set to `0` to disable (scheduler logs reference this setting explicitly). |
+Balanced safety and speed.
---
-## 🎭 How It Works
-
-### **Action Delays**
-- **Random pauses** between automation steps
-- **Natural variation** mimics human decision time
-- **Configurable range** allows fine-tuning
-
-### **Gesture Simulation**
-- **Micro mouse moves** — Tiny cursor adjustments (safe zones only)
-- **Minor scrolls** — Small page movements (non-interactive areas)
-- **Probability-based** — Not every action includes gestures
-- **Centralized controller** — The `Humanizer` service now drives all gesture + pause behavior so every module uses the same probabilities and timing windows.
-
-### **Temporal Patterns**
-- **Time windows** — Only run during specified hours
-- **Random off days** — Skip days to avoid rigid patterns
-- **Natural scheduling** — Mimics human usage patterns
-
----
-
-## 🎯 Usage Examples
-
-### **Default Setup (Recommended)**
-```json
-{
- "humanization": { "enabled": true }
-}
-```
-✅ **Best for most users** — Balanced safety and naturalness
-
-### **Minimal Humanization**
-```json
-{
- "humanization": {
- "enabled": true,
- "gestureMoveProb": 0.1,
- "gestureScrollProb": 0.1,
- "actionDelay": { "min": 100, "max": 200 }
- }
-}
-```
-⚡ **Faster execution** with minimal gestures
-
-### **Maximum Natural Behavior**
-```json
+### Conservative (More Natural)
+```jsonc
{
"humanization": {
"enabled": true,
"actionDelay": { "min": 300, "max": 800 },
"gestureMoveProb": 0.6,
"gestureScrollProb": 0.4,
- "allowedWindows": ["08:30-11:00", "19:00-22:00"],
- "randomOffDaysPerWeek": 2
- }
-}
-```
-🎭 **Most human-like** but slower execution
-
-### **Disabled Humanization**
-```json
-{
- "humanization": { "enabled": false }
-}
-```
-🚀 **Fastest execution** — automation optimized
-
----
-
-## ⏰ Time Windows
-
-### **Setup**
-```json
-{
- "humanization": {
- "enabled": true,
- "allowedWindows": ["08:00-10:30", "20:00-22:30"]
- }
-}
-```
-
-### **Behavior**
-- Script **waits** until next allowed window
-- Uses **local time** for scheduling
-- **Multiple windows** supported per day
-- **Empty array** `[]` = no time restrictions
-
-### **Examples**
-```json
-// Morning and evening windows
-"allowedWindows": ["08:00-10:30", "20:00-22:30"]
-
-// Lunch break only
-"allowedWindows": ["12:00-13:00"]
-
-// Extended evening window
-"allowedWindows": ["18:00-23:00"]
-
-// No restrictions
-"allowedWindows": []
-```
-
----
-
-## 📅 Random Off Days
-
-### **Purpose**
-Mimics natural human behavior by skipping random days per week.
-
-### **Configuration**
-```json
-{
- "humanization": {
- "randomOffDaysPerWeek": 1 // Skip 1 random day per week
- }
-}
-```
-
-### **Options**
-- `0` — Never skip days
-- `1` — Skip 1 random day per week (default)
-- `2` — Skip 2 random days per week
-- `3+` — Higher values for more irregular patterns
-
----
-
-## 🔒 Safety Features
-
-### **Safe by Design**
-- ✅ **Never clicks** arbitrary elements
-- ✅ **Gestures only** in safe zones
-- ✅ **Minor movements** — pixel-level adjustments
-- ✅ **Probability-based** — Natural randomness
-- ✅ **Non-interactive areas** — Avoids clickable elements
-
-### **Buy Mode Compatibility**
-- **Passive monitoring** remains unaffected
-- **No interference** with manual actions
-- **Background tasks** only for monitoring
-
----
-
-## 📊 Performance Impact
-
-| Setting | Speed Impact | Natural Feel | Recommendation |
-|---------|--------------|--------------|----------------|
-| **Disabled** | Fastest | Robotic | Development only |
-| **Default** | Moderate | Balanced | **Recommended** |
-| **High probability** | Slower | Very natural | Conservative users |
-| **Time windows** | Delayed start | Realistic | Scheduled execution |
-
----
-
-## 🛠️ Troubleshooting
-
-| Problem | Solution |
-|---------|----------|
-| **Script too slow** | Reduce `actionDelay` values; lower probabilities |
-| **Too robotic** | Increase probabilities; add time windows |
-| **Runs outside hours** | Check `allowedWindows` format (24-hour time) |
-| **Skipping too many days** | Reduce `randomOffDaysPerWeek` |
-| **Gestures interfering** | Lower probabilities or disable specific gestures |
-
-### **Debug Humanization**
-```powershell
-$env:DEBUG_HUMANIZATION=1; npm start
-```
-
----
-
-## 🎛️ Presets
-
-### **Conservative**
-```json
-{
- "humanization": {
- "enabled": true,
- "actionDelay": { "min": 200, "max": 600 },
- "gestureMoveProb": 0.6,
- "gestureScrollProb": 0.4,
- "allowedWindows": ["08:00-10:00", "20:00-22:00"],
"randomOffDaysPerWeek": 2
}
}
```
-### **Balanced (Default)**
-```json
-{
- "humanization": {
- "enabled": true
- }
-}
-```
+Slower but safer.
-### **Performance**
-```json
+---
+
+### Fast (Less Natural)
+```jsonc
{
"humanization": {
"enabled": true,
@@ -268,11 +93,68 @@ $env:DEBUG_HUMANIZATION=1; npm start
}
```
+Faster execution, higher risk.
+
---
-## 🔗 Related Guides
+## ⏰ Time Windows (Optional)
-- **[Getting Started](./getting-started.md)** — Initial setup and configuration
-- **[Scheduler](./schedule.md)** — Automated timing and execution
-- **[Security](./security.md)** — Privacy and detection avoidance
-- **[Buy Mode](./buy-mode.md)** — Manual purchasing with monitoring
+Run only during specific hours:
+
+```jsonc
+{
+ "humanization": {
+ "enabled": true,
+ "allowedWindows": ["08:00-10:30", "20:00-22:30"]
+ }
+}
+```
+
+Script **waits** until next allowed window if started outside.
+
+---
+
+## 📅 Random Off Days
+
+Skip random days per week:
+
+```jsonc
+{
+ "humanization": {
+ "enabled": true,
+ "randomOffDaysPerWeek": 1 // Skip 1 random day/week
+ }
+}
+```
+
+**Options:**
+- `0` — Never skip days
+- `1` — Skip 1 day/week (default)
+- `2` — Skip 2 days/week
+
+---
+
+## 🛠️ Troubleshooting
+
+| Problem | Solution |
+|---------|----------|
+| **Too slow** | Lower `actionDelay`, reduce probabilities |
+| **Too fast/robotic** | Increase delays, higher probabilities |
+| **Not running at all** | Check `allowedWindows` time format |
+
+---
+
+## 📚 Next Steps
+
+**Need vacation mode?**
+→ See [Scheduler Vacation](./schedule.md#vacation-mode)
+
+**Want scheduling?**
+→ **[Scheduler Guide](./schedule.md)**
+
+**More security?**
+→ **[Security Guide](./security.md)**
+
+---
+
+**[← Back to Hub](./index.md)** | **[Config Guide](./config.md)**
diff --git a/docs/index.md b/docs/index.md
index bcacca4..7224f39 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,62 +1,79 @@
-# 📚 Microsoft Rewards Script V2 Docs
+# 📚 Documentation Hub
-
-
-**🎯 Your companion for mastering the automation stack**
-*Curated guides, verified against the current codebase*
-
-
+**Complete guide to automate Microsoft Rewards**
---
-## 🚀 Quick Navigation
+## 🚀 Start Here (In Order)
-### **Essential Setup**
-| Guide | Why you should read it |
-|-------|------------------------|
-| **[🎬 Getting Started](./getting-started.md)** | Install, configure, and run the bot in minutes |
-| **[👤 Accounts & 2FA](./accounts.md)** | Add Microsoft accounts, enable TOTP, and secure logins |
-| **[⚙️ Configuration Reference](./config.md)** | Understand every option in `src/config.jsonc` |
+### For Complete Beginners
-### **Run & Operate**
-| Guide | Focus |
-|-------|-------|
-| **[⏰ Scheduling](./schedule.md)** | Cron-style automation and daily cadence |
-| **[🐳 Docker](./docker.md)** | Container deployment with prewired headless settings |
-| **[🛠️ Diagnostics](./diagnostics.md)** | Troubleshooting, log capture, and support checklist |
-| **[🧠 Humanization](./humanization.md)** | Natural browser behavior and ban avoidance |
-| **[🌐 Proxy Setup](./proxy.md)** | Per-account proxy routing and geo-tuning |
-| **[📊 Job State](./jobstate.md)** | How runs persist progress and recover |
-| **[🔄 Auto Update](./update.md)** | Keep the script current without manual pulls |
-| **[🛡️ Security Notes](./security.md)** | Threat model, secrets handling, and best practices |
+1. **[Getting Started](./getting-started.md)** — Install and run in 10 minutes
+2. **[Accounts & 2FA](./accounts.md)** — Add your Microsoft accounts
+3. **[Basic Config](./config.md#quick-start)** — 5 essential options
+4. **[Scheduler](./schedule.md#quick-start)** — Automate daily runs
-### **Notifications & Control**
-| Guide | Purpose |
-|-------|---------|
-| **[📱 NTFY Push](./ntfy.md)** | Real-time phone notifications |
-| **[� Discord Webhooks](./conclusionwebhook.md)** | Detailed run summaries in your server |
-
-### **Special Modes**
-| Guide | Purpose |
-|-------|---------|
-| **[💳 Buy Mode](./buy-mode.md)** | Assisted manual redemption and live monitoring |
+**You're all set! 🎉**
---
-## 🧭 Reading Paths
+## 🔥 Popular Features
-- **First install:** Getting Started → Accounts & 2FA → Configuration Reference → Scheduling **or** Docker
-- **Docker-first:** Getting Started prerequisites → Docker → Diagnostics → Notifications (NTFY or Webhooks)
-- **Optimizing runs:** Humanization → Schedule tuning → Proxy → Job State → Update
+### Notifications & Monitoring
+- **[Discord Webhooks](./conclusionwebhook.md)** — Get run summaries
+- **[NTFY Push](./ntfy.md)** — Mobile alerts
-Each guide now links back to the most relevant follow-up topics so you can jump between setup, operations, and troubleshooting without losing context.
+### Anti-Ban & Privacy
+- **[Humanization](./humanization.md)** — Natural behavior simulation
+- **[Proxy Setup](./proxy.md)** — Change your IP (optional)
+
+### Deployment
+- **[Docker](./docker.md)** — Container deployment
+- **[Diagnostics](./diagnostics.md)** — Troubleshooting
---
-## 🔗 Useful Shortcuts
+## 📖 All Documentation
-- Need sample configs? → [Config presets](./config-presets/)
-- Want a scripted environment? → [Scheduler](./schedule.md)
-- Looking to self-audit? → [Diagnostics](./diagnostics.md) + [Security](./security.md)
+### Configuration & Setup
+- [Complete Configuration Reference](./config.md) — All options explained
+- [Scheduler Setup](./schedule.md) — Automated timing
+- [Job State](./jobstate.md) — Progress tracking
+- [Auto-Update](./update.md) — Keep script current
-If something feels out of sync with the code, open an issue or ping us on Discord—the docs are maintained to match the current defaults (`src/config.jsonc`, visible browsers by default, Docker headless enforcement via `FORCE_HEADLESS=1`).
+### Advanced Features
+- [Buy Mode](./buy-mode.md) — Manual purchase monitoring
+- [Security Guide](./security.md) — Privacy & incident response
+
+---
+
+## 🆘 Need Help?
+
+**Technical issue?** → [Diagnostics Guide](./diagnostics.md)
+**Login problem?** → [Accounts & 2FA](./accounts.md#troubleshooting)
+**Banned?** → [Security Guide](./security.md)
+
+**Join Discord** → [Support Server](https://discord.gg/KRBFxxsU)
+
+---
+
+## 🎯 Quick Links by Use Case
+
+### "I just installed the script"
+→ [Getting Started](./getting-started.md) → [Accounts](./accounts.md) → [Scheduler](./schedule.md)
+
+### "I want daily automation"
+→ [Scheduler Guide](./schedule.md) → [Humanization](./humanization.md)
+
+### "I need notifications"
+→ [Discord Webhooks](./conclusionwebhook.md) or [NTFY](./ntfy.md)
+
+### "I want to use Docker"
+→ [Docker Guide](./docker.md)
+
+### "Something's broken"
+→ [Diagnostics](./diagnostics.md) → [Security](./security.md)
+
+---
+
+**[← Back to README](../README.md)**
diff --git a/docs/jobstate.md b/docs/jobstate.md
index 8ea189d..f92e052 100644
--- a/docs/jobstate.md
+++ b/docs/jobstate.md
@@ -1,339 +1,112 @@
-# 💾 Job State Persistence
+# 💾 Job State
-
-
-**🔄 Resume interrupted tasks and track progress across runs**
-*Never lose your progress again*
-
-
+**Resume interrupted tasks automatically**
---
-## 🎯 What is Job State Persistence?
+## 💡 What Is It?
-Job state persistence allows the script to **resume interrupted tasks** and **track progress** across multiple runs, ensuring no work is lost when the script is stopped or crashes.
+Saves progress after each completed task. If script crashes or stops, it resumes exactly where it left off.
-### **Key Features**
-- 🔄 **Resumable tasks** — Pick up exactly where you left off
-- 📅 **Daily tracking** — Date-specific progress monitoring
-- 👤 **Per-account isolation** — Independent progress for each account
-- 🛡️ **Corruption protection** — Atomic writes prevent data loss
-- 🚀 **Performance optimized** — Minimal overhead
+**Already enabled by default!**
+
+---
+
+## ⚡ How It Works
+
+### Progress Tracking
+
+```
+sessions/job-state/
+├── account1@email.com/
+│ ├── daily-set-2025-10-16.json
+│ ├── desktop-search-2025-10-16.json
+│ └── mobile-search-2025-10-16.json
+└── account2@email.com/
+ └── ...
+```
+
+- ✅ **Per-account** — Independent progress
+- ✅ **Date-specific** — Fresh start each day
+- ✅ **Auto-cleanup** — Old files remain for history
+
+---
+
+## 🎯 Benefits
+
+### Interrupted Runs
+
+| Scenario | Without Job State | With Job State |
+|----------|-------------------|----------------|
+| **Power outage** | Start from beginning | Resume from last task |
+| **Manual stop** | Lose all progress | Pick up where left off |
+| **Network failure** | Redo everything | Continue remaining tasks |
---
## ⚙️ Configuration
-### **Basic Setup**
-```json
+**Already enabled:**
+```jsonc
{
"jobState": {
"enabled": true,
- "dir": ""
+ "dir": "" // Empty = use default location
}
}
```
-### **Configuration Options**
-
-| Setting | Description | Default |
-|---------|-------------|---------|
-| `enabled` | Enable job state persistence | `true` |
-| `dir` | Custom directory for state files | `""` (uses `sessions/job-state`) |
-
----
-
-## 🏗️ How It Works
-
-### **State Tracking**
-- 📋 **Monitors completion** status of individual activities
-- 🔍 **Tracks progress** for daily sets, searches, and promotional tasks
-- ❌ **Prevents duplicates** when script restarts
-
-### **Storage Structure**
-```
-sessions/job-state/
-├── account1@email.com/
-│ ├── daily-set-2025-01-20.json
-│ ├── desktop-search-2025-01-20.json
-│ └── mobile-search-2025-01-20.json
-└── account2@email.com/
- ├── daily-set-2025-01-20.json
- └── promotional-tasks-2025-01-20.json
-```
-
-### **State File Format**
-```json
+**Custom location:**
+```jsonc
{
- "date": "2025-01-20",
- "account": "user@email.com",
- "type": "daily-set",
- "completed": [
- "daily-check-in",
- "quiz-1",
- "poll-1"
- ],
- "remaining": [
- "quiz-2",
- "search-desktop"
- ],
- "lastUpdate": "2025-01-20T10:30:00.000Z"
+ "jobState": {
+ "enabled": true,
+ "dir": "/custom/path/job-state"
+ }
}
```
---
-## 🚀 Key Benefits
+## 🧹 Maintenance
-### **Resumable Tasks**
-- ✅ **Script restarts** pick up where they left off
-- ✅ **Individual completion** is remembered
-- ✅ **Avoid re-doing** completed activities
+### Reset Progress (Fresh Start)
-### **Daily Reset**
-- 📅 **Date-specific** state files
-- 🌅 **New day** automatically starts fresh tracking
-- 📚 **History preserved** for analysis
-
-### **Account Isolation**
-- 👤 **Separate state** per account
-- ⚡ **Parallel processing** doesn't interfere
-- 📊 **Independent progress** tracking
-
----
-
-## 📋 Use Cases
-
-### **Interrupted Executions**
-| Scenario | Benefit |
-|----------|---------|
-| **Network issues** | Resume when connection restored |
-| **System reboots** | Continue after restart |
-| **Manual termination** | Pick up from last checkpoint |
-| **Resource exhaustion** | Recover without losing progress |
-
-### **Selective Reruns**
-| Feature | Description |
-|---------|-------------|
-| **Skip completed sets** | Avoid redoing finished daily activities |
-| **Resume searches** | Continue partial search sessions |
-| **Retry failed tasks** | Target only problematic activities |
-| **Account targeting** | Process specific accounts only |
-
-### **Progress Monitoring**
-- 📊 **Track completion rates** across accounts
-- 🔍 **Identify problematic** activities
-- ⏱️ **Monitor task duration** trends
-- 🐛 **Debug stuck** or slow tasks
-
----
-
-## 🛠️ Technical Implementation
-
-### **Checkpoint Strategy**
-- 💾 **State saved** after each completed activity
-- ⚛️ **Atomic writes** prevent corruption
-- 🔒 **Lock-free design** for concurrent access
-
-### **Performance Optimization**
-- ⚡ **Minimal I/O overhead** — Fast state updates
-- 🧠 **In-memory caching** — Reduce disk access
-- 📥 **Lazy loading** — Load state files on demand
-
-### **Error Handling**
-- 🔧 **Corrupted files** are rebuilt automatically
-- 📁 **Missing directories** created as needed
-- 🎯 **Graceful degradation** when disabled
-
----
-
-## 🗂️ File Management
-
-### **Automatic Behavior**
-- 📅 **Date-specific files** — New files for each day
-- 💾 **Preserved history** — Old files remain for reference
-- 🚀 **No auto-deletion** — Manual cleanup recommended
-
-### **Manual Maintenance**
```powershell
-# Clean state files older than 7 days
-Get-ChildItem sessions/job-state -Recurse -Filter "*.json" | Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-7)} | Remove-Item
-
-# Reset all job state (start fresh)
+# Reset all accounts
Remove-Item -Recurse -Force sessions/job-state/
-# Reset specific account state
+# Reset one account
Remove-Item -Recurse -Force sessions/job-state/user@email.com/
```
----
+### Cleanup Old Files
-## 📊 Example Workflows
-
-### **Interrupted Daily Run**
-```
-Day 1 - 10:30 AM:
-✅ Account A: Daily set completed
-🔄 Account B: 3/5 daily tasks done
-❌ Script crashes
-
-Day 1 - 2:00 PM:
-🚀 Script restarts
-✅ Account A: Skipped (already complete)
-🔄 Account B: Resumes with 2 remaining tasks
-```
-
-### **Multi-Day Tracking**
-```
-Monday:
-📅 daily-set-2025-01-20.json created
-✅ All tasks completed
-
-Tuesday:
-📅 daily-set-2025-01-21.json created
-🔄 Fresh start for new day
-📚 Monday's progress preserved
-```
-
----
-
-## 🔍 Debugging Job State
-
-### **State Inspection**
```powershell
-# View current state for account
-Get-Content sessions/job-state/user@email.com/daily-set-2025-01-20.json | ConvertFrom-Json
-
-# List all state files
-Get-ChildItem sessions/job-state -Recurse -Filter "*.json"
-```
-
-### **Debug Output**
-Enable verbose logging to see state operations:
-```powershell
-$env:DEBUG_REWARDS_VERBOSE=1; npm start
-```
-
-Sample output:
-```
-[INFO] Loading job state for user@email.com (daily-set)
-[INFO] Found 3 completed tasks, 2 remaining
-[INFO] Skipping completed task: daily-check-in
-[INFO] Starting task: quiz-2
+# Keep last 7 days only
+Get-ChildItem sessions/job-state -Recurse -Filter "*.json" | Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-7)} | Remove-Item
```
---
## 🛠️ Troubleshooting
-| Problem | Cause | Solution |
-|---------|-------|----------|
-| **Tasks not resuming** | Missing/corrupt state files | Check file permissions; verify directory exists |
-| **Duplicate execution** | Clock sync issues | Ensure system time is accurate |
-| **Excessive files** | No cleanup schedule | Implement regular state file cleanup |
-| **Permission errors** | Write access denied | Verify sessions/ directory is writable |
-
-### **Common Issues**
-
-#### **Tasks Not Resuming**
-```
-[ERROR] Failed to load job state: Permission denied
-```
-**Solutions:**
-- ✅ Check file/directory permissions
-- ✅ Verify state directory exists
-- ✅ Ensure write access to sessions/
-
-#### **Duplicate Task Execution**
-```
-[WARN] Task appears to be running twice
-```
-**Solutions:**
-- ✅ Check for corrupt state files
-- ✅ Verify system clock synchronization
-- ✅ Clear state for affected account
-
-#### **Storage Growth**
-```
-[INFO] Job state directory: 2.3GB (1,247 files)
-```
-**Solutions:**
-- ✅ Implement regular cleanup schedule
-- ✅ Remove old state files (7+ days)
-- ✅ Monitor disk space usage
+| Problem | Solution |
+|---------|----------|
+| **Tasks not resuming** | Check file permissions |
+| **Duplicate execution** | Ensure system time is accurate |
+| **Excessive files** | Implement cleanup schedule |
---
-## 🤝 Integration Features
+## 📚 Next Steps
-### **Session Persistence**
-- 🍪 **Works alongside** browser session storage
-- 🔐 **Complements** cookie and fingerprint persistence
-- 🌐 **Independent of** proxy and authentication state
+**Need scheduler?**
+→ **[Scheduler Guide](./schedule.md)**
-### **Clustering**
-- ⚡ **Isolated state** per cluster worker
-- 🚫 **No shared state** between parallel processes
-- 📁 **Worker-specific** directories
-
-### **Scheduling**
-- ⏰ **Persists across** scheduled runs
-- 🌅 **Daily reset** at midnight automatically
-- 🔄 **Long-running continuity** maintained
+**Want diagnostics?**
+→ **[Diagnostics Guide](./diagnostics.md)**
---
-## ⚙️ Advanced Configuration
-
-### **Custom State Directory**
-```json
-{
- "jobState": {
- "enabled": true,
- "dir": "/custom/path/to/state"
- }
-}
-```
-
-### **Disabling Job State**
-```json
-{
- "jobState": {
- "enabled": false
- }
-}
-```
-
-**Effects when disabled:**
-- ❌ **Tasks restart** from beginning each run
-- ❌ **No progress tracking** between sessions
-- ❌ **Potential duplicate work** on interruptions
-- ✅ **Slightly faster startup** (no state loading)
-
----
-
-## 📊 Best Practices
-
-### **Development**
-- ✅ **Enable for testing** — Consistent behavior
-- 🧹 **Clear between changes** — Fresh state for major updates
-- 🔍 **Monitor for debugging** — State files reveal execution flow
-
-### **Production**
-- ✅ **Always enabled** — Reliability is critical
-- 💾 **Regular backups** — State directory backups
-- 📊 **Monitor disk usage** — Prevent storage growth
-
-### **Maintenance**
-- 🗓️ **Weekly cleanup** — Remove old state files
-- 🔍 **Health checks** — Verify state integrity
-- 📝 **Usage monitoring** — Track storage trends
-
----
-
-## 🔗 Related Guides
-
-- **[Getting Started](./getting-started.md)** — Initial setup and configuration
-- **[Scheduler](./schedule.md)** — Automated timing and execution
-- **[Diagnostics](./diagnostics.md)** — Error capture and debugging
-- **[Security](./security.md)** — Privacy and data protection
\ No newline at end of file
+**[← Back to Hub](./index.md)** | **[Config Guide](./config.md)**
diff --git a/docs/ntfy.md b/docs/ntfy.md
index bbd3f9e..d6cdc59 100644
--- a/docs/ntfy.md
+++ b/docs/ntfy.md
@@ -1,86 +1,79 @@
# 📱 NTFY Push Notifications
-
-
-**🔔 Real-time push notifications to your devices**
-*Stay informed wherever you are*
-
-
+**Get alerts on your phone instantly**
---
-## 🎯 What is NTFY?
+## 💡 What Is NTFY?
-NTFY is a **simple HTTP-based pub-sub notification service** that sends push notifications to your phone, desktop, or web browser. Perfect for real-time alerts about script events and errors.
+Simple push notification service that sends alerts to your phone/desktop.
-### **Key Features**
-- 📱 **Mobile & Desktop** — Push to any device
-- 🆓 **Free & Open Source** — No vendor lock-in
-- 🏠 **Self-hostable** — Complete privacy control
-- ⚡ **Real-time delivery** — Instant notifications
-- 🔒 **Authentication support** — Secure topics
-
-### **Official Links**
-- **Website** — [ntfy.sh](https://ntfy.sh)
-- **Documentation** — [docs.ntfy.sh](https://docs.ntfy.sh)
-- **GitHub** — [binwiederhier/ntfy](https://github.com/binwiederhier/ntfy)
+**Free to use:** No account required for basic features.
---
-## ⚙️ Configuration
+## ⚡ Quick Start
-### **Basic Setup**
-```json
+### 1. Install NTFY App
+
+- **Android:** [Google Play](https://play.google.com/store/apps/details?id=io.heckel.ntfy)
+- **iOS:** [App Store](https://apps.apple.com/app/ntfy/id1625396347)
+
+### 2. Choose a Topic Name
+
+Pick any unique name (e.g., `rewards-myname-2025`)
+
+### 3. Subscribe in App
+
+Open NTFY app → Add subscription → Enter your topic name
+
+### 4. Configure Script
+
+**Edit** `src/config.jsonc`:
+```jsonc
{
"notifications": {
"ntfy": {
"enabled": true,
"url": "https://ntfy.sh",
- "topic": "rewards-script",
- "authToken": ""
- }
- }
-}
-```
-
-### **Configuration Options**
-
-| Setting | Description | Example |
-|---------|-------------|---------|
-| `enabled` | Enable NTFY notifications | `true` |
-| `url` | NTFY server URL | `"https://ntfy.sh"` |
-| `topic` | Notification topic name | `"rewards-script"` |
-| `authToken` | Authentication token (optional) | `"tk_abc123..."` |
-
----
-
-## 🚀 Setup Options
-
-### **Option 1: Public Service (Easiest)**
-```json
-{
- "notifications": {
- "ntfy": {
- "enabled": true,
- "url": "https://ntfy.sh",
- "topic": "your-unique-topic-name"
+ "topic": "rewards-myname-2025"
}
}
}
```
-**Pros:**
-- ✅ No server setup required
-- ✅ Always available
-- ✅ Free to use
+**That's it!** You'll get push notifications on your phone.
-**Cons:**
-- ❌ Public server (less privacy)
-- ❌ Rate limits apply
-- ❌ Dependent on external service
+---
-### **Option 2: Self-Hosted (Recommended)**
-```json
+## 🔔 What Notifications You Get
+
+- 🚨 **Errors** — Script crashes, login failures
+- ⚠️ **Warnings** — Missing points, suspicious activity
+- 🏆 **Milestones** — Account completed successfully
+- 💳 **Buy mode** — Point spending detected
+- 📊 **Summary** — End-of-run report
+
+---
+
+## 🔒 Use Private Server (Optional)
+
+### Self-Host NTFY
+
+**Docker:**
+```yaml
+services:
+ ntfy:
+ image: binwiederhier/ntfy
+ ports:
+ - "80:80"
+ volumes:
+ - ./ntfy-data:/var/lib/ntfy
+ command: serve
+```
+
+**Then configure:**
+```jsonc
{
"notifications": {
"ntfy": {
@@ -93,315 +86,33 @@ NTFY is a **simple HTTP-based pub-sub notification service** that sends push not
}
```
-**Self-Hosted Setup:**
-```yaml
-# docker-compose.yml
-version: '3.8'
-services:
- ntfy:
- image: binwiederhier/ntfy
- container_name: ntfy
- ports:
- - "80:80"
- volumes:
- - ./data:/var/lib/ntfy
- command: serve
-```
-
----
-
-## 🔒 Authentication
-
-### **When You Need Auth**
-Authentication tokens are **optional** but required for:
-- 🔐 **Private topics** with username/password
-- 🏠 **Private NTFY servers** with authentication
-- 🛡️ **Preventing spam** on your topic
-
-### **Getting an Auth Token**
-
-#### **Method 1: Command Line**
-```bash
-ntfy token
-```
-
-#### **Method 2: Web Interface**
-1. Visit your NTFY server (e.g., `https://ntfy.sh`)
-2. Go to **Account** section
-3. Generate **new access token**
-
-#### **Method 3: API**
-```bash
-curl -X POST -d '{"label":"rewards-script"}' \
- -H "Authorization: Bearer YOUR_LOGIN_TOKEN" \
- https://ntfy.sh/v1/account/tokens
-```
-
-### **Token Format**
-- Tokens start with `tk_` (e.g., `tk_abc123def456...`)
-- Use Bearer authentication format
-- Tokens are permanent until revoked
-
----
-
-## 📲 Receiving Notifications
-
-### **Mobile Apps**
-- **Android** — [NTFY on Google Play](https://play.google.com/store/apps/details?id=io.heckel.ntfy)
-- **iOS** — [NTFY on App Store](https://apps.apple.com/app/ntfy/id1625396347)
-- **F-Droid** — Available for Android
-
-### **Desktop Options**
-- **Web Interface** — Visit your NTFY server URL
-- **Desktop Apps** — Available for Linux, macOS, Windows
-- **Browser Extension** — Chrome/Firefox extensions
-
-### **Setup Steps**
-1. **Install** NTFY app on your device
-2. **Add subscription** to your topic name
-3. **Enter server URL** (if self-hosted)
-4. **Test** with a manual message
-
----
-
-## 🔔 Notification Types
-
-### **Error Notifications**
-**Priority:** Max 🚨 | **Trigger:** Script errors and failures
-```
-[ERROR] DESKTOP [LOGIN] Failed to login: Invalid credentials
-```
-
-### **Warning Notifications**
-**Priority:** High ⚠️ | **Trigger:** Important warnings
-```
-[WARN] MOBILE [SEARCH] Didn't gain expected points from search
-```
-
-### **Info Notifications**
-**Priority:** Default 🏆 | **Trigger:** Important milestones
-```
-[INFO] MAIN [TASK] Started tasks for account user@email.com
-```
-
-### **Buy Mode Notifications**
-**Priority:** High 💳 | **Trigger:** Point spending detected
-```
-💳 Spend detected (Buy Mode)
-Account: user@email.com
-Spent: -500 points
-Current: 12,500 points
-Session spent: 1,200 points
-```
-
-### **Conclusion Summary**
-**End-of-run summary with rich formatting:**
-```
-🎯 Microsoft Rewards Summary
-Accounts: 3 • 0 with issues
-Total: 15,230 -> 16,890 (+1,660)
-Average Duration: 8m 32s
-Cumulative Runtime: 25m 36s
-```
-
----
-
-## 🤝 Integration with Discord
-
-### **Complementary Setup**
-Use **both** NTFY and Discord for comprehensive monitoring:
-
-```json
-{
- "notifications": {
- "webhook": {
- "enabled": true,
- "url": "https://discord.com/api/webhooks/..."
- },
- "conclusionWebhook": {
- "enabled": true,
- "url": "https://discord.com/api/webhooks/..."
- },
- "ntfy": {
- "enabled": true,
- "url": "https://ntfy.sh",
- "topic": "rewards-script"
- }
- }
-}
-```
-
-### **Coverage Comparison**
-
-| Feature | NTFY | Discord |
-|---------|------|---------|
-| **Mobile push** | ✅ Instant | ❌ App required |
-| **Rich formatting** | ❌ Text only | ✅ Embeds + colors |
-| **Desktop alerts** | ✅ Native | ✅ App notifications |
-| **Offline delivery** | ✅ Queued | ❌ Real-time only |
-| **Self-hosted** | ✅ Easy | ❌ Complex |
-
----
-
-## 🎛️ Advanced Configuration
-
-### **Custom Topic Names**
-Use descriptive, unique topic names:
-```json
-{
- "topic": "rewards-production-server1"
-}
-{
- "topic": "msn-rewards-home-pc"
-}
-{
- "topic": "rewards-dev-testing"
-}
-```
-
-### **Environment-Specific**
-```json
-{
- "notifications": {
- "ntfy": {
- "enabled": true,
- "url": "https://ntfy.internal.lan",
- "topic": "homelab-rewards",
- "authToken": "tk_homelab_token"
- }
- }
-}
-```
-
----
-
-## 🧪 Testing & Debugging
-
-### **Manual Test Message**
-```bash
-# Public server (no auth)
-curl -d "Test message from rewards script" https://ntfy.sh/your-topic
-
-# With authentication
-curl -H "Authorization: Bearer tk_your_token" \
- -d "Authenticated test message" \
- https://ntfy.sh/your-topic
-```
-
-### **Script Debug Mode**
-```powershell
-$env:DEBUG_REWARDS_VERBOSE=1; npm start
-```
-
-### **Server Health Check**
-```bash
-# Check NTFY server status
-curl -s https://ntfy.sh/v1/health
-
-# List your topics (with auth)
-curl -H "Authorization: Bearer tk_your_token" \
- https://ntfy.sh/v1/account/topics
-```
-
---
## 🛠️ Troubleshooting
| Problem | Solution |
|---------|----------|
-| **No notifications** | Check topic spelling; verify app subscription |
-| **Auth failures** | Verify token format (`tk_`); check token validity |
-| **Wrong server** | Test server URL in browser; check HTTPS/HTTP |
-| **Rate limits** | Switch to self-hosted; reduce notification frequency |
+| **No notifications** | Check topic name matches exactly |
+| **Wrong server** | Verify URL includes `https://` |
+| **Auth failures** | Token must start with `tk_` |
-### **Common Fixes**
-- ✅ **Topic name** — Must match exactly between config and app
-- ✅ **Server URL** — Include `https://` and check accessibility
-- ✅ **Token format** — Must start with `tk_` for authentication
-- ✅ **Network** — Verify firewall/proxy settings
+### Test Manually
----
-
-## 🏠 Homelab Integration
-
-### **Official Support**
-NTFY is included in:
-- **Debian Trixie** (testing)
-- **Ubuntu** (latest versions)
-
-### **Popular Integrations**
-- **Sonarr/Radarr** — Download completion notifications
-- **Prometheus** — Alert manager integration
-- **Home Assistant** — Automation notifications
-- **Portainer** — Container status alerts
-
-### **Docker Stack Example**
-```yaml
-version: '3.8'
-services:
- ntfy:
- image: binwiederhier/ntfy
- container_name: ntfy
- ports:
- - "80:80"
- volumes:
- - ./ntfy-data:/var/lib/ntfy
- environment:
- - NTFY_BASE_URL=https://ntfy.yourdomain.com
- command: serve
-
- rewards:
- build: .
- depends_on:
- - ntfy
- environment:
- - NTFY_URL=http://ntfy:80
+```bash
+# Send test message
+curl -d "Test from rewards script" https://ntfy.sh/your-topic
```
---
-## 🔒 Privacy & Security
+## 📚 Next Steps
-### **Public Server (ntfy.sh)**
-- ⚠️ Messages pass through public infrastructure
-- ⚠️ Topic names visible in logs
-- ✅ Suitable for non-sensitive notifications
+**Want Discord too?**
+→ **[Discord Webhooks](./conclusionwebhook.md)**
-### **Self-Hosted Server**
-- ✅ Complete control over data
-- ✅ Private network deployment possible
-- ✅ Recommended for sensitive information
-
-### **Best Practices**
-- 🔐 Use **unique, non-guessable** topic names
-- 🔑 Enable **authentication** for sensitive notifications
-- 🏠 Use **self-hosted server** for maximum privacy
-- 🔄 **Regularly rotate** authentication tokens
-
-### **Data Retention**
-- 📨 Messages are **not permanently stored**
-- ⏱️ Delivery attempts **retried** for short periods
-- 🗑️ **No long-term** message history
+**Need detailed logs?**
+→ **[Diagnostics Guide](./diagnostics.md)**
---
-## ⚡ Performance Impact
-
-### **Script Performance**
-- ✅ **Minimal overhead** — Fire-and-forget notifications
-- ✅ **Non-blocking** — Failed notifications don't affect script
-- ✅ **Asynchronous** — No execution delays
-
-### **Network Usage**
-- 📊 **Low bandwidth** — Text-only messages
-- ⚡ **HTTP POST** — Simple, efficient protocol
-- 🔄 **Retry logic** — Automatic failure recovery
-
----
-
-## 🔗 Related Guides
-
-- **[Discord Webhooks](./conclusionwebhook.md)** — Rich notification embeds
-- **[Getting Started](./getting-started.md)** — Initial setup and configuration
-- **[Buy Mode](./buy-mode.md)** — Manual purchasing notifications
-- **[Security](./security.md)** — Privacy and data protection
\ No newline at end of file
+**[← Back to Hub](./index.md)** | **[Config Guide](./config.md)**
diff --git a/docs/proxy.md b/docs/proxy.md
index 706992f..94c15fe 100644
--- a/docs/proxy.md
+++ b/docs/proxy.md
@@ -1,611 +1,126 @@
-# 🌐 Proxy Configuration
+# 🌐 Proxy Setup
-
-
-**🔒 Route traffic through proxy servers for privacy and flexibility**
-*Enhanced anonymity and geographic control*
-
-
+**Route traffic through proxy servers**
---
-## 🎯 What Are Proxies?
+## 💡 Do You Need a Proxy?
-Proxies act as **intermediaries** between your script and Microsoft's servers, providing enhanced privacy, geographic flexibility, and network management capabilities.
+**Most users DON'T need proxies.** Only use if:
+- ✅ You run many accounts from same IP
+- ✅ You want geographic flexibility
+- ✅ Your IP is already flagged
-### **Key Benefits**
-- 🎭 **IP masking** — Hide your real IP address
-- 🌍 **Geographic flexibility** — Appear to browse from different locations
-- ⚡ **Rate limiting** — Distribute requests across multiple IPs
-- 🔧 **Network control** — Route traffic through specific servers
-- 🔒 **Privacy enhancement** — Add layer of anonymity
+**Otherwise, skip this guide.**
---
-## ⚙️ Configuration
+## ⚡ Quick Start
-### **Basic Setup**
-```json
-{
- "browser": {
- "proxy": {
- "enabled": false,
- "server": "proxy.example.com:8080",
- "username": "",
- "password": "",
- "bypass": []
- }
- }
-}
-```
+### Per-Account Proxy
-### **Configuration Options**
-
-| Setting | Description | Example |
-|---------|-------------|---------|
-| `enabled` | Enable proxy usage | `true` |
-| `server` | Proxy server address and port | `"proxy.example.com:8080"` |
-| `username` | Proxy authentication username | `"proxyuser"` |
-| `password` | Proxy authentication password | `"proxypass123"` |
-| `bypass` | Domains to bypass proxy | `["localhost", "*.internal.com"]` |
-
----
-
-## 🔌 Supported Proxy Types
-
-### **HTTP Proxies**
-**Most common type for web traffic**
-```json
-{
- "browser": {
- "proxy": {
- "enabled": true,
- "server": "http://proxy.example.com:8080",
- "username": "user",
- "password": "pass"
- }
- }
-}
-```
-
-### **HTTPS Proxies**
-**Encrypted proxy connections**
-```json
-{
- "browser": {
- "proxy": {
- "enabled": true,
- "server": "https://secure-proxy.example.com:8080",
- "username": "user",
- "password": "pass"
- }
- }
-}
-```
-
-### **SOCKS Proxies**
-**Support for SOCKS4 and SOCKS5**
-```json
-{
- "browser": {
- "proxy": {
- "enabled": true,
- "server": "socks5://socks-proxy.example.com:1080",
- "username": "user",
- "password": "pass"
- }
- }
-}
-```
-
----
-
-## 🏢 Popular Proxy Providers
-
-### **Residential Proxies (Recommended)**
-**High-quality IPs from real devices**
-
-#### **Top Providers**
-- **Bright Data** (formerly Luminati) — Premium quality
-- **Smartproxy** — User-friendly dashboard
-- **Oxylabs** — Enterprise-grade
-- **ProxyMesh** — Developer-focused
-
-#### **Configuration Example**
-```json
-{
- "browser": {
- "proxy": {
- "enabled": true,
- "server": "rotating-residential.brightdata.com:22225",
- "username": "customer-username-session-random",
- "password": "your-password"
- }
- }
-}
-```
-
-### **Datacenter Proxies**
-**Fast and affordable server-based IPs**
-
-#### **Popular Providers**
-- **SquidProxies** — Reliable performance
-- **MyPrivateProxy** — Dedicated IPs
-- **ProxyRack** — Budget-friendly
-- **Storm Proxies** — Rotating options
-
-#### **Configuration Example**
-```json
-{
- "browser": {
- "proxy": {
- "enabled": true,
- "server": "datacenter.squidproxies.com:8080",
- "username": "username",
- "password": "password"
- }
- }
-}
-```
-
-### **Free Proxies**
-**⚠️ Not recommended for production use**
-
-#### **Risks**
-- ❌ Unreliable connections
-- ❌ Potential security issues
-- ❌ Often blocked by services
-- ❌ Poor performance
-
----
-
-## 🔐 Authentication Methods
-
-### **Username/Password (Most Common)**
-```json
-{
- "browser": {
- "proxy": {
- "enabled": true,
- "server": "proxy.example.com:8080",
- "username": "your-username",
- "password": "your-password"
- }
- }
-}
-```
-
-### **IP Whitelisting**
-```json
-{
- "browser": {
- "proxy": {
- "enabled": true,
- "server": "proxy.example.com:8080",
- "username": "",
- "password": ""
- }
- }
-}
-```
-
-**Setup Steps:**
-1. Contact proxy provider
-2. Provide your server's IP address
-3. Configure whitelist in provider dashboard
-4. Remove credentials from config
-
-### **Session-Based Authentication**
-```json
-{
- "browser": {
- "proxy": {
- "enabled": true,
- "server": "session-proxy.example.com:8080",
- "username": "customer-session-sticky123",
- "password": "your-password"
- }
- }
-}
-```
-
----
-
-## 🚫 Bypass Configuration
-
-### **Local Development**
-**Bypass proxy for local services**
-```json
-{
- "browser": {
- "proxy": {
- "enabled": true,
- "server": "proxy.example.com:8080",
- "bypass": [
- "localhost",
- "127.0.0.1",
- "*.local",
- "*.internal"
- ]
- }
- }
-}
-```
-
-### **Specific Domains**
-**Route certain domains directly**
-```json
-{
- "browser": {
- "proxy": {
- "enabled": true,
- "server": "proxy.example.com:8080",
- "bypass": [
- "*.microsoft.com",
- "login.live.com",
- "account.microsoft.com"
- ]
- }
- }
-}
-```
-
-### **Advanced Patterns**
-```json
-{
- "browser": {
- "proxy": {
- "enabled": true,
- "server": "proxy.example.com:8080",
- "bypass": [
- "*.intranet.*",
- "192.168.*.*",
- "10.*.*.*",
- ""
- ]
- }
- }
-}
-```
-
----
-
-## 🎛️ Advanced Configurations
-
-### **Per-Account Proxies**
-**Different proxies for different accounts**
+**Edit** `src/accounts.json`:
```json
{
"accounts": [
{
- "email": "user1@example.com",
- "password": "password1",
+ "email": "your@email.com",
+ "password": "password",
"proxy": {
- "enabled": true,
- "server": "proxy1.example.com:8080"
- }
- },
- {
- "email": "user2@example.com",
- "password": "password2",
- "proxy": {
- "enabled": true,
- "server": "proxy2.example.com:8080"
+ "proxyAxios": true,
+ "url": "proxy.example.com",
+ "port": 8080,
+ "username": "proxyuser",
+ "password": "proxypass"
}
}
]
}
```
-### **Failover Configuration**
-**Multiple proxy servers for redundancy**
+**That's it!** Script uses proxy for this account only.
+
+---
+
+## 🎯 Proxy Types
+
+### HTTP Proxy (Most Common)
+
```json
{
- "browser": {
- "proxy": {
- "enabled": true,
- "servers": [
- "primary-proxy.example.com:8080",
- "backup-proxy.example.com:8080",
- "emergency-proxy.example.com:8080"
- ],
- "username": "user",
- "password": "pass"
- }
+ "proxy": {
+ "proxyAxios": true,
+ "url": "http://proxy.example.com",
+ "port": 8080,
+ "username": "user",
+ "password": "pass"
}
}
```
-### **Geographic Routing**
-**Location-specific proxy selection**
+### SOCKS5 Proxy
+
```json
{
- "browser": {
- "proxy": {
- "enabled": true,
- "regions": {
- "us": "us-proxy.example.com:8080",
- "eu": "eu-proxy.example.com:8080",
- "asia": "asia-proxy.example.com:8080"
- },
- "defaultRegion": "us"
- }
+ "proxy": {
+ "proxyAxios": true,
+ "url": "socks5://proxy.example.com",
+ "port": 1080,
+ "username": "user",
+ "password": "pass"
}
}
```
---
-## 🔒 Security & Environment Variables
+## 🏢 Recommended Providers
-### **Credential Protection**
-**Secure proxy authentication**
+### Residential Proxies (Best)
+- **Bright Data** — Premium quality, expensive
+- **Smartproxy** — User-friendly
+- **Oxylabs** — Enterprise-grade
-**Environment Variables:**
-```powershell
-# Set in environment
-$env:PROXY_USERNAME="your-username"
-$env:PROXY_PASSWORD="your-password"
-```
+### Datacenter Proxies (Cheaper)
+- **SquidProxies** — Reliable
+- **MyPrivateProxy** — Dedicated IPs
-**Configuration:**
-```json
-{
- "browser": {
- "proxy": {
- "enabled": true,
- "server": "proxy.example.com:8080",
- "username": "${PROXY_USERNAME}",
- "password": "${PROXY_PASSWORD}"
- }
- }
-}
-```
-
-### **HTTPS Verification**
-```json
-{
- "browser": {
- "proxy": {
- "enabled": true,
- "server": "proxy.example.com:8080",
- "verifySSL": true,
- "rejectUnauthorized": true
- }
- }
-}
-```
-
-### **Connection Encryption**
-```json
-{
- "browser": {
- "proxy": {
- "enabled": true,
- "server": "https://encrypted-proxy.example.com:8080",
- "tls": {
- "enabled": true,
- "version": "TLSv1.3"
- }
- }
- }
-}
-```
-
----
-
-## 🧪 Testing & Debugging
-
-### **Manual Tests**
-```bash
-# Test proxy connection
-curl --proxy proxy.example.com:8080 http://httpbin.org/ip
-
-# Test with authentication
-curl --proxy user:pass@proxy.example.com:8080 http://httpbin.org/ip
-
-# Test geolocation
-curl --proxy proxy.example.com:8080 http://ipinfo.io/json
-```
-
-### **Script Debug Mode**
-```powershell
-$env:DEBUG_PROXY=1; npm start
-```
-
-### **Health Check Script**
-```bash
-#!/bin/bash
-PROXY="proxy.example.com:8080"
-curl --proxy $PROXY --connect-timeout 10 http://httpbin.org/status/200
-echo "Proxy health: $?"
-```
+⚠️ **Avoid free proxies** — Unreliable and often blocked.
---
## 🛠️ Troubleshooting
-| Problem | Error | Solution |
-|---------|-------|----------|
-| **Connection Failed** | `ECONNREFUSED` | Verify server address/port; check firewall |
-| **Auth Failed** | `407 Proxy Authentication Required` | Verify username/password; check IP whitelist |
-| **Timeout** | `Request timeout` | Increase timeout values; try different server |
-| **SSL Error** | `certificate verify failed` | Disable SSL verification; update certificates |
+| Problem | Solution |
+|---------|----------|
+| **"Connection refused"** | Check proxy URL and port |
+| **"407 Auth required"** | Verify username/password |
+| **"Timeout"** | Try different proxy server |
+| **"SSL error"** | Use HTTP instead of HTTPS |
-### **Common Error Messages**
+### Test Proxy Manually
-#### **Connection Issues**
-```
-[ERROR] Proxy connection failed: ECONNREFUSED
-```
-**Solutions:**
-- ✅ Verify proxy server address and port
-- ✅ Check proxy server is running
-- ✅ Confirm firewall allows connections
-- ✅ Test with different proxy server
+```bash
+# Windows (PowerShell)
+curl --proxy http://user:pass@proxy.com:8080 http://httpbin.org/ip
-#### **Authentication Issues**
-```
-[ERROR] Proxy authentication failed: 407 Proxy Authentication Required
-```
-**Solutions:**
-- ✅ Verify username and password
-- ✅ Check account is active with provider
-- ✅ Confirm IP is whitelisted (if applicable)
-- ✅ Try different authentication method
-
-#### **Performance Issues**
-```
-[ERROR] Proxy timeout: Request timeout
-```
-**Solutions:**
-- ✅ Increase timeout values
-- ✅ Check proxy server performance
-- ✅ Try different proxy server
-- ✅ Reduce concurrent connections
-
----
-
-## ⚡ Performance Optimization
-
-### **Connection Settings**
-```json
-{
- "browser": {
- "proxy": {
- "enabled": true,
- "server": "proxy.example.com:8080",
- "timeouts": {
- "connect": 30000,
- "request": 60000,
- "idle": 120000
- },
- "connectionPooling": true,
- "maxConnections": 10
- }
- }
-}
-```
-
-### **Compression Settings**
-```json
-{
- "browser": {
- "proxy": {
- "enabled": true,
- "server": "proxy.example.com:8080",
- "compression": true,
- "gzip": true
- }
- }
-}
-```
-
-### **Monitoring Metrics**
-- **Connection Success Rate** — % of successful proxy connections
-- **Response Time** — Average request latency through proxy
-- **Bandwidth Usage** — Data transferred through proxy
-- **Error Rate** — % of failed requests via proxy
-
----
-
-## 🐳 Container Integration
-
-### **Docker Environment**
-```dockerfile
-# Dockerfile
-ENV PROXY_ENABLED=true
-ENV PROXY_SERVER=proxy.example.com:8080
-ENV PROXY_USERNAME=user
-ENV PROXY_PASSWORD=pass
-```
-
-### **Kubernetes ConfigMap**
-```yaml
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: rewards-proxy-config
-data:
- proxy.json: |
- {
- "enabled": true,
- "server": "proxy.example.com:8080",
- "username": "user",
- "password": "pass"
- }
-```
-
-### **Environment-Specific**
-```json
-{
- "development": {
- "proxy": { "enabled": false }
- },
- "staging": {
- "proxy": {
- "enabled": true,
- "server": "staging-proxy.example.com:8080"
- }
- },
- "production": {
- "proxy": {
- "enabled": true,
- "server": "prod-proxy.example.com:8080"
- }
- }
-}
+# Linux/macOS
+curl --proxy http://user:pass@proxy.com:8080 http://httpbin.org/ip
```
---
-## 📊 Best Practices
+## 📚 Next Steps
-### **Proxy Selection**
-- 🏆 **Residential > Datacenter** — Better for avoiding detection
-- 💰 **Paid > Free** — Reliability and security
-- 🔄 **Multiple providers** — Redundancy and failover
-- 🌍 **Geographic diversity** — Flexibility and compliance
+**Proxy working?**
+→ **[Setup Scheduler](./schedule.md)**
-### **Configuration Management**
-- 🔑 **Environment variables** — Secure credential storage
-- 🧪 **Test before deploy** — Verify configuration works
-- 📊 **Monitor performance** — Track availability and speed
-- 🔄 **Backup configs** — Ready failover options
+**Need humanization?**
+→ **[Humanization Guide](./humanization.md)**
-### **Security Guidelines**
-- 🔒 **HTTPS proxies** — Encrypted connections when possible
-- 🛡️ **SSL verification** — Verify certificates
-- 🔄 **Rotate credentials** — Regular password updates
-- 👁️ **Monitor access** — Watch for unauthorized usage
+**Multiple accounts?**
+→ **[Accounts Guide](./accounts.md)**
---
-## ⚖️ Legal & Compliance
-
-### **Terms of Service**
-- 📋 Review Microsoft's Terms of Service
-- 📄 Understand proxy provider's acceptable use policy
-- 🌍 Ensure compliance with local regulations
-- 🗺️ Consider geographic restrictions
-
-### **Data Privacy**
-- 🔍 Understand data flow through proxy
-- 📝 Review proxy provider's data retention policies
-- 🔐 Implement additional encryption if needed
-- 📊 Monitor proxy logs and access
-
-### **Rate Limiting**
-- ⏱️ Respect Microsoft's rate limits
-- ⏸️ Implement proper delays between requests
-- 🚦 Monitor for IP blocking or throttling
-- 🔄 Use proxy rotation to distribute load
-
----
-
-## 🔗 Related Guides
-
-- **[Getting Started](./getting-started.md)** — Initial setup and configuration
-- **[Security](./security.md)** — Privacy and data protection
-- **[Docker](./docker.md)** — Container deployment with proxies
-- **[Humanization](./humanization.md)** — Natural behavior patterns
\ No newline at end of file
+**[← Back to Hub](./index.md)** | **[Config Guide](./config.md)**
diff --git a/docs/schedule.md b/docs/schedule.md
index a504bed..155a1ec 100644
--- a/docs/schedule.md
+++ b/docs/schedule.md
@@ -1,232 +1,37 @@
-# ⏰ Scheduler & Automation
+# ⏰ Scheduler
-
-
-**🚀 Built-in scheduler for automated daily execution**
-*Set it and forget it*
-
-
+**Automate daily script execution**
---
-## 🎯 What is the Scheduler?
+## ⚡ Quick Start
-The built-in scheduler provides **automated script execution** at specified times without requiring external cron jobs or task schedulers.
+### Basic Setup
-### **Key Features**
-- 📅 **Daily automation** — Run at the same time every day
-- 🌍 **Timezone aware** — Handles DST automatically
-- 🔄 **Multiple passes** — Execute script multiple times per run
-- 🏖️ **Vacation mode** — Skip random days monthly
-- 🎲 **Jitter support** — Randomize execution times
-- 📅 **Humanization off-days** — Weekly random skips (disable via `humanization.randomOffDaysPerWeek`)
-- ⚡ **Immediate start** — Option to run on startup
-
----
-
-## ⚙️ Configuration
-
-### **Basic Setup**
-```json
+**Edit** `src/config.jsonc`:
+```jsonc
{
"schedule": {
"enabled": true,
"time": "09:00",
- "timeZone": "America/New_York",
- "runImmediatelyOnStart": true
- },
- "passesPerRun": 2
-}
-```
-
-### **Advanced Setup with Vacation Mode**
-```json
-{
- "schedule": {
- "enabled": true,
- "time": "10:00",
- "timeZone": "Europe/Paris",
- "runImmediatelyOnStart": false
- },
- "passesPerRun": 3,
- "vacation": {
- "enabled": true,
- "minDays": 3,
- "maxDays": 5
+ "timeZone": "America/New_York"
}
}
```
-### **Configuration Options**
-
-| Setting | Default | Description |
-|---------|---------|-------------|
-| `enabled` | `false` | Enable built-in scheduler |
-| `time` | `"09:00"` | Daily execution time (24-hour format) |
-| `timeZone` | `"UTC"` | IANA timezone identifier |
-| `runImmediatelyOnStart` | `true` | Execute once on process startup |
-| `passesPerRun` | `1` | Number of complete runs per execution |
-| `vacation.enabled` | `false` | Skip random monthly off-block |
-| `vacation.minDays` | `3` | Minimum vacation days |
-| `vacation.maxDays` | `5` | Maximum vacation days |
-| `cron` | `undefined` | Optional cron expression (string or array) for advanced scheduling |
-
-### **Cron Expressions (Advanced)**
-
-You can now drive the scheduler with classic cron syntax instead of a single daily time. Provide either a string or an array in `schedule.cron`.
-
-```json
-{
- "schedule": {
- "enabled": true,
- "cron": [
- "0 7 * * *", // every day at 07:00
- "30 19 * * 1-5" // weekdays at 19:30
- ],
- "timeZone": "Europe/Paris"
- }
-}
-```
-
-- Supports 5-field and 6-field cron expressions (`second minute hour day month weekday`).
-- When `cron` is set, the legacy `time`, `time12`, `time24`, and daily jitter env vars are ignored.
-- The scheduler still honors vacation mode, weekly random off-days, run-on-start, and watchdog features.
-
----
-
-## 🚀 How It Works
-
-### **Daily Scheduling**
-1. **Calculate next run** — Timezone-aware scheduling
-2. **Wait until time** — Minimal resource usage
-3. **Execute passes** — Run script specified number of times
-4. **Schedule next day** — Automatic DST adjustment
-
-### **Startup Behavior**
-
-#### **Immediate Start Enabled (`true`)**
-- **Before scheduled time** → Run immediately + wait for next scheduled time
-- **After scheduled time** → Run immediately + wait for tomorrow's time
-
-#### **Immediate Start Disabled (`false`)**
-- **Any time** → Always wait for next scheduled time
-
-### **Multiple Passes**
-- Each pass processes **all accounts** through **all tasks**
-- Useful for **maximum point collection**
-- Higher passes = **more points** but **increased detection risk**
-
----
-
-## 🏖️ Vacation Mode
-
-### **Monthly Off-Blocks**
-Vacation mode randomly selects a **contiguous block of days** each month to skip execution.
-
-### **Configuration**
-```json
-{
- "vacation": {
- "enabled": true,
- "minDays": 3,
- "maxDays": 5
- }
-}
-```
-
-### **How It Works**
-- **Random selection** — Different days each month
-- **Contiguous block** — Skip consecutive days, not scattered
-- **Independent** — Works with weekly random off-days
-- **Logged** — Shows selected vacation period
-
-### **Example Output**
-```
-[SCHEDULE] Selected vacation block this month: 2025-01-15 → 2025-01-18
-[SCHEDULE] Skipping run - vacation mode (3 days remaining)
-```
-
----
-
-## 🌍 Supported Timezones
-
-### **North America**
-- `America/New_York` — Eastern Time
-- `America/Chicago` — Central Time
-- `America/Denver` — Mountain Time
-- `America/Los_Angeles` — Pacific Time
-- `America/Phoenix` — Arizona (no DST)
-
-### **Europe**
-- `Europe/London` — GMT/BST
-- `Europe/Paris` — CET/CEST
-- `Europe/Berlin` — CET/CEST
-- `Europe/Rome` — CET/CEST
-- `Europe/Moscow` — MSK
-
-### **Asia Pacific**
-- `Asia/Tokyo` — JST
-- `Asia/Shanghai` — CST
-- `Asia/Kolkata` — IST
-- `Australia/Sydney` — AEST/AEDT
-- `Pacific/Auckland` — NZST/NZDT
-
----
-
-## 🎲 Randomization & Watchdog
-
-### **Environment Variables**
-```powershell
-# Add random delay before first run (5-20 minutes)
-$env:SCHEDULER_INITIAL_JITTER_MINUTES_MIN=5
-$env:SCHEDULER_INITIAL_JITTER_MINUTES_MAX=20
-
-# Add daily jitter to scheduled time (2-10 minutes)
-$env:SCHEDULER_DAILY_JITTER_MINUTES_MIN=2
-$env:SCHEDULER_DAILY_JITTER_MINUTES_MAX=10
-
-# Kill stuck passes after N minutes
-$env:SCHEDULER_PASS_TIMEOUT_MINUTES=180
-
-# Run each pass in separate process (recommended)
-$env:SCHEDULER_FORK_PER_PASS=true
-```
-
-### **Benefits**
-- **Avoid patterns** — Prevents exact-time repetition
-- **Protection** — Kills stuck processes
-- **Isolation** — Process separation for stability
-
----
-
-## 🖥️ Running the Scheduler
-
-### **Development Mode**
-```powershell
-npm run ts-schedule
-```
-
-### **Production Mode**
-```powershell
-npm run build
+**Start scheduler:**
+```bash
npm run start:schedule
```
-### **Background Execution**
-```powershell
-# Windows Background (PowerShell)
-Start-Process -NoNewWindow -FilePath "npm" -ArgumentList "run", "start:schedule"
-
-# Alternative: Windows Task Scheduler (recommended)
-# Create scheduled task via GUI or schtasks command
-```
+**That's it!** Script runs automatically at 9 AM daily.
---
-## 📊 Usage Examples
+## 🎯 Common Configurations
-### **Basic Daily Automation**
-```json
+### Morning Run
+```jsonc
{
"schedule": {
"enabled": true,
@@ -235,437 +40,140 @@ Start-Process -NoNewWindow -FilePath "npm" -ArgumentList "run", "start:schedule"
}
}
```
-⏰ **Perfect for morning routine** — Catch daily resets
-### **Multiple Daily Passes**
-```json
-{
- "schedule": {
- "enabled": true,
- "time": "10:00",
- "timeZone": "Europe/London",
- "runImmediatelyOnStart": false
- },
- "passesPerRun": 3
-}
-```
-🔄 **Maximum points** with higher detection risk
-
-### **Conservative with Vacation**
-```json
+### Evening Run
+```jsonc
{
"schedule": {
"enabled": true,
"time": "20:00",
- "timeZone": "America/Los_Angeles"
- },
- "passesPerRun": 1,
- "vacation": {
- "enabled": true,
- "minDays": 4,
- "maxDays": 6
+ "timeZone": "Europe/Paris"
}
}
```
-🏖️ **Natural patterns** with monthly breaks
+
+### Multiple Passes Per Day
+```jsonc
+{
+ "schedule": {
+ "enabled": true,
+ "time": "10:00",
+ "timeZone": "America/Los_Angeles"
+ },
+ "passesPerRun": 2
+}
+```
---
-## 🐳 Docker Integration
+## 🌍 Common Timezones
-### **Built-in Scheduler (Recommended)**
-```yaml
-services:
- microsoft-rewards-script:
- build: .
- environment:
- TZ: Europe/Paris
- command: ["npm", "run", "start:schedule"]
-```
-- Uses `passesPerRun` from config
-- Single long-running process
-- No external cron needed
+| Region | Timezone |
+|--------|----------|
+| **US East** | `America/New_York` |
+| **US West** | `America/Los_Angeles` |
+| **UK** | `Europe/London` |
+| **France** | `Europe/Paris` |
+| **Germany** | `Europe/Berlin` |
-### **External Cron (Project Default)**
-```yaml
-services:
- microsoft-rewards-script:
- build: .
- environment:
- CRON_SCHEDULE: "0 7,16,20 * * *"
- RUN_ON_START: "true"
-```
-- Uses `run_daily.sh` with random delays
-- Multiple cron executions
-- Lockfile prevents overlaps
+[All timezones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
---
-## 📋 Logging Output
+## 🎲 Advanced: Cron Expressions
-### **Scheduler Initialization**
-```
-[SCHEDULE] Scheduler initialized for daily 09:00 America/New_York
-[SCHEDULE] Next run scheduled for 2025-01-21 09:00:00 EST
+Want more control? Use cron:
+
+```jsonc
+{
+ "schedule": {
+ "enabled": true,
+ "cron": "0 9 * * *", // Every day at 9 AM
+ "timeZone": "America/New_York"
+ }
+}
```
-### **Daily Execution**
-```
-[SCHEDULE] Starting scheduled run (pass 1 of 2)
-[SCHEDULE] Completed scheduled run in 12m 34s
-[SCHEDULE] Next run scheduled for 2025-01-22 09:00:00 EST
+### Cron Examples
+```bash
+"0 7 * * *" # Every day at 7:00 AM
+"30 20 * * *" # Every day at 8:30 PM
+"0 9,21 * * *" # Twice daily: 9 AM and 9 PM
+"0 10 * * 1-5" # Weekdays only at 10 AM
```
-### **Time Calculations**
-```
-[SCHEDULE] Current time: 2025-01-20 15:30:00 EDT
-[SCHEDULE] Target time: 2025-01-21 09:00:00 EDT
-[SCHEDULE] Waiting 17h 30m until next run
+[Cron syntax helper](https://crontab.guru/)
+
+---
+
+## 🏖️ Vacation Mode (Optional)
+
+Skip random days each month to look more natural:
+
+```jsonc
+{
+ "vacation": {
+ "enabled": true,
+ "minDays": 3,
+ "maxDays": 5
+ }
+}
```
+**Example:** Script will randomly skip 3-5 consecutive days per month.
+
---
## 🛠️ Troubleshooting
| Problem | Solution |
|---------|----------|
-| **Scheduler not running** | Check `enabled: true`; verify timezone format |
-| **Wrong execution time** | Verify system clock; check DST effects |
-| **Memory growth** | Restart process weekly; monitor logs |
-| **Missed executions** | Check system sleep/hibernation; verify process |
-
-### **Debug Commands**
-```powershell
-# Test timezone calculation
-node -e "console.log(new Date().toLocaleString('en-US', {timeZone: 'America/New_York'}))"
-
-# Verify config syntax
-node -e "const fs=require('fs');const strip=input=>{let out='',inString=false,stringChar='',inLine=false,inBlock=false;for(let i=0;i schedule.log 2>&1 &
-
-# Windows (background service - requires additional setup)
-# Recommend using Task Scheduler or Windows Service wrapper
-```
-
-## Process Management
-
-### Long-Running Process
-- Scheduler runs continuously
-- Automatically handles timezone changes
-- Graceful handling of system clock adjustments
-
-### Memory Management
-- Minimal memory footprint between runs
-- Garbage collection after each execution
-- No memory leaks in long-running processes
-
-### Error Recovery
-- Failed runs don't affect future scheduling
-- Automatic retry on next scheduled time
-- Error logging for troubleshooting
-
-## Logging Output
-
-### Scheduler Events
-```
-[SCHEDULE] Scheduler initialized for daily 09:00 America/New_York
-[SCHEDULE] Next run scheduled for 2025-09-21 09:00:00 EST
-[SCHEDULE] Starting scheduled run (pass 1 of 2)
-[SCHEDULE] Completed scheduled run in 12m 34s
-[SCHEDULE] Next run scheduled for 2025-09-22 09:00:00 EST
-```
-
-### Time Calculations
-```
-[SCHEDULE] Current time: 2025-09-20 15:30:00 EDT
-[SCHEDULE] Target time: 2025-09-21 09:00:00 EDT
-[SCHEDULE] Waiting 17h 30m until next run
-```
-
-## Integration with Other Features
-
-### Docker Compatibility
-- Scheduler works in Docker containers
-- Alternative to external cron jobs
-- Timezone handling in containerized environments
-
-### Buy Mode Exclusion
-- Scheduler only runs automation mode
-- Buy mode (`-buy`) ignores scheduler settings
-- Manual executions bypass scheduler
-
-### Clustering
-- Scheduler runs only in single-process mode
-- Clustering disabled when scheduler is active
-- Use scheduler OR clustering, not both
-
-## Best Practices
-
-### Optimal Timing
-- **Morning runs**: Catch daily resets and new activities
-- **Evening runs**: Complete remaining tasks before midnight
-- **Avoid peak hours**: Reduce detection risk during high traffic
-
-### Timezone Selection
-- Use your local timezone for easier monitoring
-- Consider Microsoft Rewards server timezone
-- Account for daylight saving time changes
-
-### Multiple Passes
-- **2-3 passes**: Good balance of points vs. detection risk
-- **More passes**: Higher detection risk
-- **Single pass**: Safest but may miss some points
-
-### Monitoring
-- Check logs regularly for errors
-- Monitor point collection trends
-- Verify scheduler is running as expected
-
-## Troubleshooting
-
-### Common Issues
-
-**Scheduler not running:**
-- Check `enabled: true` in config
-- Verify timezone format is correct
-- Ensure no syntax errors in config.jsonc (remember it allows comments)
-
-**Wrong execution time:**
-- Verify system clock is accurate
-- Check timezone identifier spelling
-- Consider daylight saving time effects
-
-**Memory growth over time:**
-- Restart scheduler process weekly
-- Monitor system resource usage
-- Check for memory leaks in logs
-
-**Missed executions:**
-- System was sleeping/hibernating
-- Process was killed or crashed
-- Clock was adjusted significantly
+| **Scheduler not running** | Check `enabled: true` in config |
+| **Wrong execution time** | Verify timezone spelling |
+| **Runs multiple times** | Only use ONE scheduler instance |
+| **Missed run** | Check if computer was off/sleeping |
### Debug Commands
-```bash
-# Test timezone calculation
+
+**Check timezone:**
+```powershell
node -e "console.log(new Date().toLocaleString('en-US', {timeZone: 'America/New_York'}))"
-
-# Verify config syntax
-node -e "const fs=require('fs');const strip=input=>{let out='',inString=false,stringChar='',inLine=false,inBlock=false;for(let i=0;i
-
-**🛡️ Comprehensive security measures and incident response**
-*Protect your accounts and maintain privacy*
-
-
+**Protect your accounts and handle security incidents**
---
-## 🎯 Security Overview
+## ⚠️ Important Disclaimer
-This guide explains how the script **detects security-related issues**, what it does automatically, and how you can **resolve incidents** safely.
+**Using automation violates Microsoft's Terms of Service.**
-### **Security Features**
-- 🚨 **Automated detection** — Recognizes account compromise attempts
-- 🛑 **Emergency halting** — Stops all automation during incidents
-- 🔔 **Strong alerts** — Immediate notifications via Discord/NTFY
-- 📋 **Recovery guidance** — Step-by-step incident resolution
-- 🔒 **Privacy protection** — Local-only operation by default
+Your accounts **may be banned**. Use at your own risk.
---
-## 🚨 Security Incidents & Resolutions
+## 🛡️ Best Practices
-### **Recovery Email Mismatch**
+### ✅ DO
-#### **Symptoms**
-During Microsoft login, the page shows a masked recovery email like `ko*****@hacker.net` that **doesn't match** your expected recovery email pattern.
+- **Enable humanization** — Natural behavior reduces detection
+- **Use 2FA/TOTP** — More secure authentication
+- **Run 1-2x daily max** — Don't be greedy
+- **Test on secondary accounts** — Never risk your main account
+- **Enable vacation mode** — Random off days look natural
+- **Monitor regularly** — Check diagnostics and logs
-#### **What the Script Does**
-- 🛑 **Halts automation** for the current account (leaves page open for manual action)
-- 🚨 **Sends strong alerts** to all channels and engages global standby
-- ⏸️ **Stops processing** — No further accounts are processed
-- 🔔 **Repeats reminders** every 5 minutes until intervention
+### ❌ DON'T
-#### **Likely Causes**
-- ⚠️ **Account takeover** — Recovery email changed by someone else
-- 🔄 **Recent change** — You changed recovery email but forgot to update config
+- **Run on main account** — Too risky
+- **Schedule hourly** — Obvious bot pattern
+- **Ignore warnings** — Security alerts matter
+- **Use shared proxies** — Higher detection risk
+- **Skip humanization** — Robotic behavior gets caught
-#### **How to Fix**
-1. **🔍 Verify account security** in Microsoft Account settings
-2. **📝 Update config** if you changed recovery email yourself:
+---
+
+## 🚨 Security Incidents
+
+### Recovery Email Mismatch
+
+**What:** Login shows unfamiliar recovery email (e.g., `ko*****@hacker.net`)
+
+**Action:**
+1. **Stop immediately** — Script halts automatically
+2. **Check Microsoft Account** → Security settings
+3. **Update config** if you changed email yourself:
```json
{
- "email": "your@email.com",
"recoveryEmail": "ko*****@hacker.net"
}
```
-3. **🔐 Change password** and review sign-in activity if compromise suspected
-4. **🚀 Restart script** to resume normal operation
-
-#### **Prevention**
-- ✅ Keep `recoveryEmail` in `accounts.json` up to date
-- ✅ Use strong unique passwords and MFA
-- ✅ Regular security reviews
+4. **Change password** if compromise suspected
---
-### **"We Can't Sign You In" (Blocked)**
+### "We Can't Sign You In" (Blocked)
-#### **Symptoms**
-Microsoft presents a page titled **"We can't sign you in"** during login attempts.
+**What:** Microsoft blocks login attempt
-#### **What the Script Does**
-- 🛑 **Stops automation** and leaves page open for manual recovery
-- 🚨 **Sends strong alert** with high priority notifications
-- ⏸️ **Engages global standby** to avoid processing other accounts
-
-#### **Likely Causes**
-- ⏱️ **Temporary lock** — Rate limiting or security check from Microsoft
-- 🚫 **Account restrictions** — Ban related to unusual activity
-- 🔒 **Verification required** — SMS code, authenticator, or other challenges
-
-#### **How to Fix**
-1. **✅ Complete verification** challenges (SMS, authenticator, etc.)
-2. **⏸️ Pause activity** for 24-48h if blocked repeatedly
-3. **🔧 Reduce concurrency** and increase delays between actions
-4. **🌐 Check proxies** — Ensure consistent IP/country
-5. **📞 Appeal if needed** — Contact Microsoft if ban is suspected
-
-#### **Prevention**
-- ✅ **Respect rate limits** — Use humanization settings
-- ✅ **Avoid patterns** — Don't run too many accounts from same IP
-- ✅ **Geographic consistency** — Use proxies from your actual region
-- ✅ **Human-like timing** — Avoid frequent credential retries
+**Action:**
+1. **Wait 24-48 hours** — Temporary locks usually lift
+2. **Complete any challenges** — SMS, authenticator, etc.
+3. **Reduce frequency** — Run less often
+4. **Enable humanization** — If not already enabled
+5. **Check proxy** — Ensure consistent IP/location
---
-## 🔐 Privacy & Data Protection
+## 🔐 Account Security
-### **Local-First Architecture**
-- 💾 **All data local** — Credentials, sessions, logs stored locally only
-- 🚫 **No telemetry** — Zero data collection or external reporting
-- 🔒 **No cloud storage** — Everything remains on your machine
+### Strong Credentials
-### **Credential Security**
```json
{
"accounts": [
{
- "email": "user@example.com",
- "password": "secure-password-here",
- "totpSecret": "optional-2fa-secret"
+ "email": "your@email.com",
+ "password": "strong-unique-password",
+ "totp": "JBSWY3DPEHPK3PXP"
}
]
}
```
-**Best Practices:**
-- 🔐 **Strong passwords** — Unique, complex passwords per account
-- 🔑 **2FA enabled** — Time-based one-time passwords when possible
-- 📁 **File permissions** — Restrict access to `accounts.json`
-- 🔄 **Regular rotation** — Change passwords periodically
+- ✅ **Unique passwords** per account
+- ✅ **TOTP enabled** for all accounts
+- ✅ **Strong passwords** (16+ characters)
+- 🔄 **Rotate every 90 days**
-### **Session Management**
-- 🍪 **Persistent cookies** — Stored locally in `sessions/` directory
-- 🔒 **Encrypted storage** — Session data protected at rest
-- ⏰ **Automatic expiry** — Old sessions cleaned up automatically
-- 🗂️ **Per-account isolation** — No session data mixing
+### File Permissions
+
+```bash
+# Linux/macOS - Restrict access
+chmod 600 src/accounts.json
+
+# Windows - Right-click → Properties → Security
+# Remove all users except yourself
+```
---
## 🌐 Network Security
-### **Proxy Configuration**
+### Use Proxies (Optional)
+
```json
{
- "browser": {
- "proxy": {
- "enabled": true,
- "server": "proxy.example.com:8080",
- "username": "user",
- "password": "pass"
- }
+ "proxy": {
+ "proxyAxios": true,
+ "url": "proxy.example.com",
+ "port": 8080,
+ "username": "user",
+ "password": "pass"
}
}
```
-**Security Benefits:**
-- 🎭 **IP masking** — Hide your real IP address
-- 🌍 **Geographic flexibility** — Appear from different locations
-- 🔒 **Traffic encryption** — HTTPS proxy connections
-- 🛡️ **Detection avoidance** — Rotate IPs to avoid patterns
+**Benefits:**
+- IP masking
+- Geographic flexibility
+- Reduces pattern detection
-### **Traffic Analysis Protection**
-- 🔐 **HTTPS only** — All Microsoft communications encrypted
-- 🚫 **No plaintext passwords** — Credentials protected in transit
-- 🛡️ **Certificate validation** — SSL/TLS verification enabled
-- 🔍 **Deep packet inspection** resistant
+→ **[Full Proxy Guide](./proxy.md)**
---
-## 🛡️ Anti-Detection Measures
+## 📊 Monitoring
-### **Humanization**
-```json
+### Enable Diagnostics
+
+```jsonc
{
- "humanization": {
+ "diagnostics": {
"enabled": true,
- "actionDelay": { "min": 150, "max": 450 },
- "gestureMoveProb": 0.4,
- "gestureScrollProb": 0.2
+ "saveScreenshot": true,
+ "saveHtml": true
}
}
```
-**Natural Behavior Simulation:**
-- ⏱️ **Random delays** — Variable timing between actions
-- 🖱️ **Mouse movements** — Subtle cursor adjustments
-- 📜 **Scrolling gestures** — Natural page interactions
-- 🎲 **Randomized patterns** — Avoid predictable automation
+→ **[Diagnostics Guide](./diagnostics.md)**
-### **Browser Fingerprinting**
-- 🌐 **Real user agents** — Authentic browser identification
-- 📱 **Platform consistency** — Mobile/desktop specific headers
-- 🔧 **Plugin simulation** — Realistic browser capabilities
-- 🖥️ **Screen resolution** — Appropriate viewport dimensions
+### Enable Notifications
----
-
-## 📊 Monitoring & Alerting
-
-### **Real-Time Monitoring**
-```json
+```jsonc
{
- "notifications": {
- "webhook": {
- "enabled": true,
- "url": "https://discord.com/api/webhooks/..."
- },
- "ntfy": {
- "enabled": true,
- "url": "https://ntfy.sh",
- "topic": "rewards-security"
- }
+ "conclusionWebhook": {
+ "enabled": true,
+ "url": "https://discord.com/api/webhooks/..."
}
}
```
-**Alert Types:**
-- 🚨 **Security incidents** — Account compromise attempts
-- ⚠️ **Login failures** — Authentication issues
-- 🔒 **Account blocks** — Access restrictions detected
-- 📊 **Performance anomalies** — Unusual execution patterns
-
-### **Log Analysis**
-- 📝 **Detailed logging** — All actions recorded locally
-- 🔍 **Error tracking** — Failed operations highlighted
-- 📊 **Performance metrics** — Timing and success rates
-- 🛡️ **Security events** — Incident timeline reconstruction
+→ **[Webhook Setup](./conclusionwebhook.md)**
---
-## 🧪 Security Testing
+## 🛠️ Incident Response
-### **Penetration Testing**
-```powershell
-# Test credential handling
-$env:DEBUG_SECURITY=1; npm start
+### Account Compromised
-# Test session persistence
-$env:DEBUG_SESSIONS=1; npm start
+1. **Stop all automation**
+2. **Change password immediately**
+3. **Check sign-in activity** in Microsoft Account
+4. **Enable 2FA** if not already
+5. **Review security info** (recovery email, phone)
+6. **Contact Microsoft** if unauthorized access
-# Test proxy configuration
-$env:DEBUG_PROXY=1; npm start
-```
+### Temporary Ban
-### **Vulnerability Assessment**
-- 🔍 **Regular audits** — Check for security issues
-- 📦 **Dependency scanning** — Monitor npm packages
-- 🔒 **Code review** — Manual security analysis
-- 🛡️ **Threat modeling** — Identify attack vectors
+1. **Pause automation** for 48-72 hours
+2. **Reduce frequency** when resuming
+3. **Increase delays** in humanization
+4. **Use proxy** from your region
+5. **Monitor closely** after resuming
---
-## 📋 Security Checklist
+## 🔗 Privacy Tips
-### **Initial Setup**
-- ✅ **Strong passwords** for all accounts
-- ✅ **2FA enabled** where possible
-- ✅ **File permissions** restricted to user only
-- ✅ **Proxy configured** if desired
-- ✅ **Notifications set up** for alerts
-
-### **Regular Maintenance**
-- ✅ **Password rotation** every 90 days
-- ✅ **Session cleanup** weekly
-- ✅ **Log review** for anomalies
-- ✅ **Security updates** for dependencies
-- ✅ **Backup verification** of configurations
-
-### **Incident Response**
-- ✅ **Alert investigation** within 15 minutes
-- ✅ **Account verification** when suspicious
-- ✅ **Password changes** if compromise suspected
-- ✅ **Activity review** in Microsoft account settings
-- ✅ **Documentation** of incidents and resolutions
+- 🔐 **Local-only** — All data stays on your machine
+- 🚫 **No telemetry** — Script doesn't phone home
+- 📁 **File security** — Restrict permissions
+- 🔄 **Regular backups** — Keep config backups
+- 🗑️ **Clean logs** — Delete old diagnostics
---
-## 🚨 Emergency Procedures
+## 📚 Next Steps
-### **Account Compromise Response**
-1. **🛑 Immediate shutdown** — Stop all script activity
-2. **🔒 Change passwords** — Update all affected accounts
-3. **📞 Contact Microsoft** — Report unauthorized access
-4. **🔍 Audit activity** — Review recent sign-ins and changes
-5. **🛡️ Enable additional security** — Add 2FA, recovery options
-6. **📋 Document incident** — Record timeline and actions taken
+**Setup humanization?**
+→ **[Humanization Guide](./humanization.md)**
-### **Detection Evasion**
-1. **⏸️ Temporary suspension** — Pause automation for 24-48h
-2. **🔧 Reduce intensity** — Lower pass counts and frequencies
-3. **🌐 Change IPs** — Rotate proxies or VPN endpoints
-4. **⏰ Adjust timing** — Modify scheduling patterns
-5. **🎭 Increase humanization** — More natural behavior simulation
+**Need proxies?**
+→ **[Proxy Guide](./proxy.md)**
+
+**Want monitoring?**
+→ **[Diagnostics](./diagnostics.md)**
---
-## 🔗 Quick Reference Links
-
-When the script detects a security incident, it opens this guide directly to the relevant section:
-
-- **[Recovery Email Mismatch](#recovery-email-mismatch)** — Email change detection
-- **[Account Blocked](#we-cant-sign-you-in-blocked)** — Login restriction handling
-
----
-
-## 🔗 Related Guides
-
-- **[Getting Started](./getting-started.md)** — Initial setup and configuration
-- **[Accounts & 2FA](./accounts.md)** — Microsoft account setup
-- **[Proxy Configuration](./proxy.md)** — Network privacy and routing
-- **[Humanization](./humanization.md)** — Natural behavior patterns
+**[← Back to Hub](./index.md)** | **[Config Guide](./config.md)**
diff --git a/docs/update.md b/docs/update.md
index 9efad73..2c86c69 100644
--- a/docs/update.md
+++ b/docs/update.md
@@ -1,371 +1,48 @@
-# 🔄 Auto-Update System
+# 🔄 Auto-Update
-
-
-**🚀 Automatic updates to keep your installation current**
-*Set it and forget it*
-
-
+**Keep script up to date automatically**
---
-## 🎯 What is Auto-Update?
+## 💡 What Is It?
-The automatic update system runs **after script completion** to keep your installation current with the latest features, bug fixes, and security patches.
+After each run, script checks for updates and installs them automatically.
-### **Key Features**
-- 🔄 **Automatic updates** — Runs after each script completion
-- 🛡️ **Safe by design** — Fast-forward only Git updates
-- 🐳 **Docker support** — Container image updates
-- 🛠️ **Custom scripts** — Extensible update process
-- 🔒 **Error resilient** — Failed updates don't break main script
+**Already enabled by default!**
+
+---
+
+## ⚡ How It Works
+
+### After Each Run
+
+1. **Fetch latest** from GitHub
+2. **Pull changes** (safe fast-forward only)
+3. **Install dependencies** (`npm ci`)
+4. **Rebuild** (`npm run build`)
+
+**No action needed from you!**
---
## ⚙️ Configuration
-### **Basic Setup**
-```json
+```jsonc
{
"update": {
- "git": true,
- "docker": false,
- "scriptPath": "setup/update/update.mjs"
- }
-}
-```
-
-### **Configuration Options**
-
-| Setting | Description | Default |
-|---------|-------------|---------|
-| `git` | Enable Git-based updates | `true` |
-| `docker` | Enable Docker container updates | `false` |
-| `scriptPath` | Path to custom update script | `"setup/update/update.mjs"` |
-
----
-
-## 🚀 Update Methods
-
-### **Git Updates (`git: true`)**
-
-#### **What It Does**
-- 📥 **Fetches latest changes** from remote repository
-- ⚡ **Fast-forward only pulls** (safe updates)
-- 📦 **Reinstalls dependencies** (`npm ci`)
-- 🔨 **Rebuilds the project** (`npm run build`)
-
-#### **Requirements**
-- ✅ Git installed and available in PATH
-- ✅ Repository is a Git clone (not downloaded ZIP)
-- ✅ No uncommitted local changes
-- ✅ Internet connectivity
-
-#### **Process**
-```bash
-git fetch --all --prune
-git pull --ff-only
-npm ci
-npm run build
-```
-
-### **Docker Updates (`docker: true`)**
-
-#### **What It Does**
-- 📥 **Pulls latest container images**
-- 🔄 **Restarts services** with new images
-- 💾 **Preserves configurations** and mounted volumes
-
-#### **Requirements**
-- ✅ Docker and Docker Compose installed
-- ✅ `docker-compose.yml` file present
-- ✅ Proper container registry access
-
-#### **Process**
-```bash
-docker compose pull
-docker compose up -d
-```
-
----
-
-## 🛠️ Custom Update Scripts
-
-### **Default Script**
-- **Path** — `setup/update/update.mjs`
-- **Format** — ES modules
-- **Arguments** — Command line flags
-
-### **Script Arguments**
-- `--git` — Enable Git update process
-- `--docker` — Enable Docker update process
-- Both flags can be combined
-
-### **Custom Script Example**
-```javascript
-// custom-update.mjs
-import { execSync } from 'child_process'
-
-const args = process.argv.slice(2)
-
-if (args.includes('--git')) {
- console.log('🔄 Running custom Git update...')
- execSync('git pull && npm install', { stdio: 'inherit' })
-}
-
-if (args.includes('--docker')) {
- console.log('🐳 Running custom Docker update...')
- execSync('docker-compose pull && docker-compose up -d', { stdio: 'inherit' })
-}
-```
-
----
-
-## ⏰ Execution Timing
-
-### **When Updates Run**
-| Scenario | Update Runs |
-|----------|-------------|
-| **Normal completion** | ✅ All accounts processed successfully |
-| **Error completion** | ✅ Script finished with errors but completed |
-| **Interruption** | ❌ Script killed or crashed mid-execution |
-
-### **Update Sequence**
-1. **🏁 Main script completion** — All accounts processed
-2. **📊 Conclusion webhook** sent (if enabled)
-3. **🚀 Update process begins**
-4. **📥 Git updates** (if enabled)
-5. **🐳 Docker updates** (if enabled)
-6. **🔚 Process exits**
-
----
-
-## 🛡️ Safety Features
-
-### **Git Safety**
-- ⚡ **Fast-forward only** — Prevents overwriting local changes
-- 📦 **Dependency verification** — Ensures `npm ci` succeeds
-- 🔨 **Build validation** — Confirms TypeScript compilation works
-
-### **Error Handling**
-- ✅ **Update failures** don't break main script
-- 🔇 **Silent failures** — Errors logged but don't crash process
-- 🔄 **Rollback protection** — Failed updates don't affect current installation
-
-### **Concurrent Execution**
-- 🔒 **Single update process** — Multiple instances don't conflict
-- 🚫 **Lock-free design** — No file locking needed
-- 🎯 **Independent updates** — Each script copy updates separately
-
----
-
-## 📊 Monitoring Updates
-
-### **Log Output**
-```
-[UPDATE] Starting post-run update process
-[UPDATE] Git update enabled, Docker update disabled
-[UPDATE] Running: git fetch --all --prune
-[UPDATE] Running: git pull --ff-only
-[UPDATE] Running: npm ci
-[UPDATE] Running: npm run build
-[UPDATE] Update completed successfully
-```
-
-### **Update Verification**
-```powershell
-# Check if updates are pending
-git status
-
-# View recent commits
-git log --oneline -5
-
-# Verify build status
-npm run build
-```
-
----
-
-## 📋 Use Cases
-
-### **Development Environment**
-| Benefit | Description |
-|---------|-------------|
-| **Synchronized** | Keep local installation current with repository |
-| **Automated** | Automatic dependency updates |
-| **Seamless** | Integration of bug fixes and features |
-
-### **Production Deployment**
-| Benefit | Description |
-|---------|-------------|
-| **Security** | Automated security patches |
-| **Features** | Updates without manual intervention |
-| **Consistent** | Same update process across servers |
-
-### **Docker Environments**
-| Benefit | Description |
-|---------|-------------|
-| **Images** | Container image updates |
-| **Security** | Patches in base images |
-| **Automated** | Service restarts |
-
----
-
-## 📋 Best Practices
-
-### **Git Configuration**
-- 🧹 **Clean working directory** — Commit or stash local changes
-- 🌿 **Stable branch** — Use `main` or `stable` for auto-updates
-- 📝 **Regular commits** — Keep repository history clean
-- 💾 **Backup data** — Sessions and accounts before updates
-
-### **Docker Configuration**
-- 🏷️ **Image tagging** — Use specific tags, not `latest` for production
-- 💾 **Volume persistence** — Ensure data volumes are mounted
-- 🔗 **Service dependencies** — Configure proper startup order
-- 🎯 **Resource limits** — Set appropriate memory and CPU limits
-
-### **Monitoring**
-- 📝 **Check logs regularly** — Monitor update success/failure
-- 🧪 **Test after updates** — Verify script functionality
-- 💾 **Backup configurations** — Preserve working setups
-- 📊 **Version tracking** — Record successful versions
-
----
-
-## 🛠️ Troubleshooting
-
-### **Git Issues**
-
-| Error | Solution |
-|-------|----------|
-| **"Not a git repository"** | Clone repository instead of downloading ZIP |
-| **"Local changes would be overwritten"** | Commit or stash local changes |
-| **"Fast-forward not possible"** | Repository diverged - reset to remote state |
-
-#### **Git Reset Command**
-```powershell
-# Reset to remote state (⚠️ loses local changes)
-git fetch origin
-git reset --hard origin/main
-```
-
-### **Docker Issues**
-
-| Error | Solution |
-|-------|----------|
-| **"Docker not found"** | Install Docker and Docker Compose |
-| **"Permission denied"** | Add user to docker group |
-| **"No docker-compose.yml"** | Create compose file or use custom script |
-
-#### **Docker Permission Fix**
-```powershell
-# Windows: Ensure Docker Desktop is running
-# Linux: Add user to docker group
-sudo usermod -aG docker $USER
-```
-
-### **Network Issues**
-
-| Error | Solution |
-|-------|----------|
-| **"Could not resolve host"** | Check internet connectivity |
-| **"Connection timeout"** | Check firewall and proxy settings |
-
----
-
-## 🔧 Manual Updates
-
-### **Git Manual Update**
-```powershell
-git fetch --all --prune
-git pull --ff-only
-npm ci
-npm run build
-```
-
-### **Docker Manual Update**
-```powershell
-docker compose pull
-docker compose up -d
-```
-
-### **Dependencies Only**
-```powershell
-npm ci
-npm run build
-```
-
----
-
-## ⚙️ Update Configuration
-
-### **Complete Disable**
-```json
-{
- "update": {
- "git": false,
- "docker": false
- }
-}
-```
-
-### **Selective Enable**
-```json
-{
- "update": {
- "git": true, // Keep Git updates
- "docker": false // Disable Docker updates
- }
-}
-```
-
-### **Custom Script Path**
-```json
-{
- "update": {
- "git": true,
- "docker": false,
- "scriptPath": "my-custom-update.mjs"
+ "git": true, // Auto-update from Git
+ "docker": false // Docker container updates (if using Docker)
}
}
```
---
-## 🔒 Security Considerations
+## 🐳 Docker Updates
-### **Git Security**
-- ✅ **Trusted remote** — Updates pull from configured remote only
-- ⚡ **Fast-forward only** — Prevents malicious rewrites
-- 📦 **NPM registry** — Dependencies from official registry
+If using Docker:
-### **Docker Security**
-- 🏷️ **Verified images** — Container images from configured registries
-- ✍️ **Image signatures** — Verify when possible
-- 🔍 **Security scanning** — Regular scanning of base images
-
-### **Script Execution**
-- 👤 **Same permissions** — Update scripts run with same privileges
-- 🚫 **No escalation** — No privilege escalation during updates
-- 🔍 **Review scripts** — Custom scripts should be security reviewed
-
----
-
-## 🎯 Environment Examples
-
-### **Development**
-```json
-{
- "update": {
- "git": true,
- "docker": false
- }
-}
-```
-
-### **Production**
-```json
+```jsonc
{
"update": {
"git": false,
@@ -374,22 +51,54 @@ npm run build
}
```
-### **Hybrid**
-```json
-{
- "update": {
- "git": true,
- "docker": true,
- "scriptPath": "setup/update/production-update.mjs"
- }
-}
+Pulls latest Docker image and restarts container.
+
+---
+
+## 🛠️ Manual Update
+
+### Git
+```bash
+git pull
+npm ci
+npm run build
+```
+
+### Docker
+```bash
+docker compose pull
+docker compose up -d
```
---
-## 🔗 Related Guides
+## ⚠️ Troubleshooting
-- **[Getting Started](./getting-started.md)** — Initial setup and configuration
-- **[Docker](./docker.md)** — Container deployment and management
-- **[Scheduler](./schedule.md)** — Automated timing and execution
-- **[Security](./security.md)** — Privacy and data protection
\ No newline at end of file
+| Problem | Solution |
+|---------|----------|
+| **"Not a git repository"** | Clone repo (don't download ZIP) |
+| **"Local changes"** | Commit or stash your changes |
+| **"Update failed"** | Check internet connection |
+
+### Reset to Remote
+
+```bash
+git fetch origin
+git reset --hard origin/v2
+npm ci
+npm run build
+```
+
+---
+
+## 📚 Next Steps
+
+**Need security tips?**
+→ **[Security Guide](./security.md)**
+
+**Setup scheduler?**
+→ **[Scheduler Guide](./schedule.md)**
+
+---
+
+**[← Back to Hub](./index.md)** | **[Config Guide](./config.md)**
diff --git a/package-lock.json b/package-lock.json
index d825b5e..8dff8c9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "microsoft-rewards-script",
- "version": "2.1.5",
+ "version": "2.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "microsoft-rewards-script",
- "version": "2.1.5",
+ "version": "2.3.0",
"license": "ISC",
"dependencies": {
"axios": "^1.8.4",
@@ -34,7 +34,7 @@
"typescript": "^5.5.4"
},
"engines": {
- "node": ">=18.0.0"
+ "node": ">=20.0.0"
},
"funding": {
"type": "github",
diff --git a/package.json b/package.json
index 93c0675..8947da1 100644
--- a/package.json
+++ b/package.json
@@ -1,11 +1,11 @@
{
"name": "microsoft-rewards-script",
- "version": "2.1.5",
+ "version": "2.3.0",
"description": "Automatically do tasks for Microsoft Rewards but in TS!",
"private": true,
"main": "index.js",
"engines": {
- "node": ">=18.0.0"
+ "node": ">=20.0.0"
},
"repository": {
"type": "git",
diff --git a/src/browser/BrowserFunc.ts b/src/browser/BrowserFunc.ts
index a206a33..0c6e159 100644
--- a/src/browser/BrowserFunc.ts
+++ b/src/browser/BrowserFunc.ts
@@ -4,9 +4,10 @@ import { AxiosRequestConfig } from 'axios'
import { MicrosoftRewardsBot } from '../index'
import { saveSessionData } from '../util/Load'
+import { TIMEOUTS, RETRY_LIMITS, SELECTORS, URLS } from '../constants'
-import { Counters, DashboardData, MorePromotion, PromotionalItem } from './../interface/DashboardData'
-import { QuizData } from './../interface/QuizData'
+import { Counters, DashboardData, MorePromotion, PromotionalItem } from '../interface/DashboardData'
+import { QuizData } from '../interface/QuizData'
import { AppUserData } from '../interface/AppUserData'
import { EarnablePoints } from '../interface/Points'
@@ -34,34 +35,47 @@ export default class BrowserFunc {
await page.goto(this.bot.config.baseURL)
- const maxIterations = 5 // Maximum iterations set to 5
-
- for (let iteration = 1; iteration <= maxIterations; iteration++) {
- await this.bot.utils.wait(3000)
+ for (let iteration = 1; iteration <= RETRY_LIMITS.GO_HOME_MAX; iteration++) {
+ await this.bot.utils.wait(TIMEOUTS.LONG)
await this.bot.browser.utils.tryDismissAllMessages(page)
- // Check if account is suspended (multiple heuristics)
- const suspendedByHeader = await page.waitForSelector('#suspendedAccountHeader', { state: 'visible', timeout: 1500 }).then(() => true).catch(() => false)
- let suspendedByText = false
- if (!suspendedByHeader) {
- try {
- const text = (await page.textContent('body')) || ''
- suspendedByText = /account has been suspended|suspended due to unusual activity/i.test(text)
- } catch { /* ignore */ }
- }
- if (suspendedByHeader || suspendedByText) {
- this.bot.log(this.bot.isMobile, 'GO-HOME', 'This account appears suspended!', 'error')
- throw new Error('Account has been suspended!')
- }
-
try {
- // If activities are found, exit the loop
- await page.waitForSelector('#more-activities', { timeout: 1000 })
+ // If activities are found, exit the loop (SUCCESS - account is OK)
+ await page.waitForSelector(SELECTORS.MORE_ACTIVITIES, { timeout: 1000 })
this.bot.log(this.bot.isMobile, 'GO-HOME', 'Visited homepage successfully')
break
} catch (error) {
- // Continue if element is not found
+ // Activities not found yet - check if it's because account is suspended
+ // Only check suspension if we can't find activities (reduces false positives)
+ const suspendedByHeader = await page.waitForSelector(SELECTORS.SUSPENDED_ACCOUNT, { state: 'visible', timeout: 500 }).then(() => true).catch(() => false)
+
+ if (suspendedByHeader) {
+ this.bot.log(this.bot.isMobile, 'GO-HOME', `Account suspension detected by header selector (iteration ${iteration})`, 'error')
+ throw new Error('Account has been suspended!')
+ }
+
+ // Secondary check: look for suspension text in main content area only
+ try {
+ const mainContent = (await page.locator('#contentContainer, #main, .main-content').first().textContent({ timeout: 500 }).catch(() => '')) || ''
+ const suspensionPatterns = [
+ /account\s+has\s+been\s+suspended/i,
+ /suspended\s+due\s+to\s+unusual\s+activity/i,
+ /your\s+account\s+is\s+temporarily\s+suspended/i
+ ]
+
+ const isSuspended = suspensionPatterns.some(pattern => pattern.test(mainContent))
+ if (isSuspended) {
+ this.bot.log(this.bot.isMobile, 'GO-HOME', `Account suspension detected by content text (iteration ${iteration})`, 'error')
+ throw new Error('Account has been suspended!')
+ }
+ } catch (e) {
+ // Ignore errors in text check - not critical
+ this.bot.log(this.bot.isMobile, 'GO-HOME', `Suspension text check skipped: ${e}`, 'warn')
+ }
+
+ // Not suspended, just activities not loaded yet - continue to next iteration
+ this.bot.log(this.bot.isMobile, 'GO-HOME', `Activities not found yet (iteration ${iteration}/${RETRY_LIMITS.GO_HOME_MAX}), retrying...`, 'warn')
}
// Below runs if the homepage was unable to be visited
@@ -70,14 +84,14 @@ export default class BrowserFunc {
if (currentURL.hostname !== dashboardURL.hostname) {
await this.bot.browser.utils.tryDismissAllMessages(page)
- await this.bot.utils.wait(2000)
+ await this.bot.utils.wait(TIMEOUTS.MEDIUM_LONG)
await page.goto(this.bot.config.baseURL)
} else {
this.bot.log(this.bot.isMobile, 'GO-HOME', 'Visited homepage successfully')
break
}
- await this.bot.utils.wait(5000)
+ await this.bot.utils.wait(TIMEOUTS.VERY_LONG)
}
} catch (error) {
@@ -127,6 +141,14 @@ export default class BrowserFunc {
}
}
+ // Wait a bit longer for scripts to load, especially on mobile
+ await this.bot.utils.wait(this.bot.isMobile ? TIMEOUTS.LONG : TIMEOUTS.MEDIUM)
+
+ // Wait for the more-activities element to ensure page is fully loaded
+ await target.waitForSelector(SELECTORS.MORE_ACTIVITIES, { timeout: TIMEOUTS.DASHBOARD_WAIT }).catch(() => {
+ this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', 'Activities element not found, continuing anyway', 'warn')
+ })
+
let scriptContent = await target.evaluate(() => {
const scripts = Array.from(document.querySelectorAll('script'))
const targetScript = scripts.find(script => script.innerText.includes('var dashboard'))
@@ -135,18 +157,36 @@ export default class BrowserFunc {
})
if (!scriptContent) {
- await this.bot.browser.utils.captureDiagnostics(target, 'dashboard-data-missing').catch(()=>{})
+ this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', 'Dashboard script not found on first try, attempting recovery', 'warn')
+ await this.bot.browser.utils.captureDiagnostics(target, 'dashboard-data-missing').catch((e) => {
+ this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', `Failed to capture diagnostics: ${e}`, 'warn')
+ })
+
// Force a navigation retry once before failing hard
try {
await this.goHome(target)
- await target.waitForLoadState('domcontentloaded', { timeout: 5000 }).catch(()=>{})
- } catch {/* ignore */}
+ await target.waitForLoadState('domcontentloaded', { timeout: TIMEOUTS.VERY_LONG }).catch((e) => {
+ this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', `Wait for load state failed: ${e}`, 'warn')
+ })
+ await this.bot.utils.wait(this.bot.isMobile ? TIMEOUTS.LONG : TIMEOUTS.MEDIUM)
+ } catch (e) {
+ this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', `Recovery navigation failed: ${e}`, 'warn')
+ }
+
const retryContent = await target.evaluate(() => {
const scripts = Array.from(document.querySelectorAll('script'))
const targetScript = scripts.find(script => script.innerText.includes('var dashboard'))
return targetScript?.innerText ? targetScript.innerText : null
}).catch(()=>null)
+
if (!retryContent) {
+ // Log additional debug info
+ const scriptsDebug = await target.evaluate(() => {
+ const scripts = Array.from(document.querySelectorAll('script'))
+ return scripts.map(s => s.innerText.substring(0, 100)).join(' | ')
+ }).catch(() => 'Unable to get script debug info')
+
+ this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', `Available scripts preview: ${scriptsDebug}`, 'warn')
throw this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', 'Dashboard data not found within script', 'error')
}
scriptContent = retryContent
@@ -154,18 +194,37 @@ export default class BrowserFunc {
// Extract the dashboard object from the script content
const dashboardData = await target.evaluate((scriptContent: string) => {
- // Extract the dashboard object using regex
- const regex = /var dashboard = (\{.*?\});/s
- const match = regex.exec(scriptContent)
+ // Try multiple regex patterns for better compatibility
+ const patterns = [
+ /var dashboard = (\{.*?\});/s, // Original pattern
+ /var dashboard=(\{.*?\});/s, // No spaces
+ /var\s+dashboard\s*=\s*(\{.*?\});/s, // Flexible whitespace
+ /dashboard\s*=\s*(\{[\s\S]*?\});/ // More permissive
+ ]
- if (match && match[1]) {
- return JSON.parse(match[1])
+ for (const regex of patterns) {
+ const match = regex.exec(scriptContent)
+ if (match && match[1]) {
+ try {
+ return JSON.parse(match[1])
+ } catch (e) {
+ // Try next pattern if JSON parsing fails
+ continue
+ }
+ }
}
+ return null
+
}, scriptContent)
if (!dashboardData) {
- await this.bot.browser.utils.captureDiagnostics(target, 'dashboard-data-parse').catch(()=>{})
+ // Log a snippet of the script content for debugging
+ const scriptPreview = scriptContent.substring(0, 200)
+ this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', `Script preview: ${scriptPreview}`, 'warn')
+ await this.bot.browser.utils.captureDiagnostics(target, 'dashboard-data-parse').catch((e) => {
+ this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', `Failed to capture diagnostics: ${e}`, 'warn')
+ })
throw this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', 'Unable to parse dashboard script', 'error')
}
@@ -263,7 +322,7 @@ export default class BrowserFunc {
: 'us'
const userDataRequest: AxiosRequestConfig = {
- url: 'https://prod.rewardsplatform.microsoft.com/dapi/me?channel=SAAndroid&options=613',
+ url: URLS.APP_USER_DATA,
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
@@ -319,38 +378,73 @@ export default class BrowserFunc {
*/
async getQuizData(page: Page): Promise {
try {
+ // Wait for page to be fully loaded
+ await page.waitForLoadState('domcontentloaded')
+ await this.bot.utils.wait(TIMEOUTS.MEDIUM)
+
const html = await page.content()
const $ = load(html)
- const scriptContent = $('script')
- .toArray()
- .map(el => $(el).text())
- .find(t => t.includes('_w.rewardsQuizRenderInfo')) || ''
+ // Try multiple possible variable names
+ const possibleVariables = [
+ '_w.rewardsQuizRenderInfo',
+ 'rewardsQuizRenderInfo',
+ '_w.quizRenderInfo',
+ 'quizRenderInfo'
+ ]
- if (scriptContent) {
- const regex = /_w\.rewardsQuizRenderInfo\s*=\s*({.*?});/s
+ let scriptContent = ''
+ let foundVariable = ''
+
+ for (const varName of possibleVariables) {
+ scriptContent = $('script')
+ .toArray()
+ .map(el => $(el).text())
+ .find(t => t.includes(varName)) || ''
+
+ if (scriptContent) {
+ foundVariable = varName
+ break
+ }
+ }
+
+ if (scriptContent && foundVariable) {
+ // Escape dots in variable name for regex
+ const escapedVar = foundVariable.replace(/\./g, '\\.')
+ const regex = new RegExp(`${escapedVar}\\s*=\\s*({.*?});`, 's')
const match = regex.exec(scriptContent)
if (match && match[1]) {
const quizData = JSON.parse(match[1])
+ this.bot.log(this.bot.isMobile, 'GET-QUIZ-DATA', `Found quiz data using variable: ${foundVariable}`, 'log')
return quizData
} else {
- throw this.bot.log(this.bot.isMobile, 'GET-QUIZ-DATA', 'Quiz data not found within script', 'error')
+ throw this.bot.log(this.bot.isMobile, 'GET-QUIZ-DATA', `Variable ${foundVariable} found but could not extract JSON data`, 'error')
}
} else {
+ // Log available scripts for debugging
+ const allScripts = $('script')
+ .toArray()
+ .map(el => $(el).text())
+ .filter(t => t.length > 0)
+ .map(t => t.substring(0, 100))
+
+ this.bot.log(this.bot.isMobile, 'GET-QUIZ-DATA', `Script not found. Tried variables: ${possibleVariables.join(', ')}`, 'error')
+ this.bot.log(this.bot.isMobile, 'GET-QUIZ-DATA', `Found ${allScripts.length} scripts on page`, 'warn')
+
throw this.bot.log(this.bot.isMobile, 'GET-QUIZ-DATA', 'Script containing quiz data not found', 'error')
}
} catch (error) {
- throw this.bot.log(this.bot.isMobile, 'GET-QUIZ-DATA', 'An error occurred:' + error, 'error')
+ throw this.bot.log(this.bot.isMobile, 'GET-QUIZ-DATA', 'An error occurred: ' + error, 'error')
}
}
async waitForQuizRefresh(page: Page): Promise {
try {
- await page.waitForSelector('span.rqMCredits', { state: 'visible', timeout: 10000 })
- await this.bot.utils.wait(2000)
+ await page.waitForSelector(SELECTORS.QUIZ_CREDITS, { state: 'visible', timeout: TIMEOUTS.DASHBOARD_WAIT })
+ await this.bot.utils.wait(TIMEOUTS.MEDIUM_LONG)
return true
} catch (error) {
@@ -361,8 +455,8 @@ export default class BrowserFunc {
async checkQuizCompleted(page: Page): Promise {
try {
- await page.waitForSelector('#quizCompleteContainer', { state: 'visible', timeout: 2000 })
- await this.bot.utils.wait(2000)
+ await page.waitForSelector(SELECTORS.QUIZ_COMPLETE, { state: 'visible', timeout: TIMEOUTS.MEDIUM_LONG })
+ await this.bot.utils.wait(TIMEOUTS.MEDIUM_LONG)
return true
} catch (error) {
@@ -402,7 +496,7 @@ export default class BrowserFunc {
// Save cookies
await saveSessionData(this.bot.config.sessionPath, browser, email, this.bot.isMobile)
- await this.bot.utils.wait(2000)
+ await this.bot.utils.wait(TIMEOUTS.MEDIUM_LONG)
// Close browser
await browser.close()
diff --git a/src/browser/BrowserUtil.ts b/src/browser/BrowserUtil.ts
index 3525aa2..610f341 100644
--- a/src/browser/BrowserUtil.ts
+++ b/src/browser/BrowserUtil.ts
@@ -40,6 +40,12 @@ export default class BrowserUtil {
closeButtons: 'button[aria-label*="close" i], button:has-text("Close"), button:has-text("Dismiss"), button:has-text("Got it"), button:has-text("OK"), button:has-text("Ok")'
} as const
+ private static readonly TERMS_UPDATE_SELECTORS = {
+ titleId: '#iTOUTitle',
+ titleText: /we're updating our terms/i,
+ nextButton: 'button[data-testid="primaryButton"]:has-text("Next"), button[type="submit"]:has-text("Next")'
+ } as const
+
constructor(bot: MicrosoftRewardsBot) {
this.bot = bot
}
@@ -57,6 +63,7 @@ export default class BrowserUtil {
count += await this.dismissStandardButtons(page)
count += await this.dismissOverlayButtons(page)
count += await this.dismissStreakDialog(page)
+ count += await this.dismissTermsUpdateDialog(page)
return count
}
@@ -135,6 +142,35 @@ export default class BrowserUtil {
}
}
+ private async dismissTermsUpdateDialog(page: Page): Promise {
+ try {
+ const { titleId, titleText, nextButton } = BrowserUtil.TERMS_UPDATE_SELECTORS
+
+ // Check if terms update page is present
+ const titleById = page.locator(titleId)
+ const titleByText = page.locator('h1').filter({ hasText: titleText })
+
+ const hasTitle = await titleById.isVisible({ timeout: 200 }).catch(() => false) ||
+ await titleByText.first().isVisible({ timeout: 200 }).catch(() => false)
+
+ if (!hasTitle) return 0
+
+ // Click the Next button
+ const nextBtn = page.locator(nextButton).first()
+ if (await nextBtn.isVisible({ timeout: 500 }).catch(() => false)) {
+ await nextBtn.click({ timeout: 1000 }).catch(() => {})
+ this.bot.log(this.bot.isMobile, 'DISMISS-ALL-MESSAGES', 'Dismissed: Terms Update Dialog (Next)')
+ // Wait a bit for navigation
+ await page.waitForTimeout(1000)
+ return 1
+ }
+
+ return 0
+ } catch {
+ return 0
+ }
+ }
+
async getLatestTab(page: Page): Promise {
try {
await this.bot.utils.wait(1000)
diff --git a/src/config.jsonc b/src/config.jsonc
index ab360cc..dcfc373 100644
--- a/src/config.jsonc
+++ b/src/config.jsonc
@@ -1,54 +1,112 @@
{
- // Base URL for Rewards dashboard and APIs (do not change unless you know what you're doing)
+ // ============================================================
+ // 🌐 GENERAL CONFIGURATION
+ // ============================================================
+
+ // Base URL for Microsoft Rewards dashboard (do not change unless necessary)
"baseURL": "https://rewards.bing.com",
- // Where to store sessions (cookies, fingerprints)
+
+ // 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": {
- // Keep headless=false so the browser window stays visible by default
+ // false = visible window | true = headless mode (invisible)
"headless": false,
- // Max time to wait for common operations (supports ms/s/min: e.g. 30000, "30s", "2min")
+ // Max timeout for operations (supports: 30000, "30s", "2min")
"globalTimeout": "30s"
},
- "execution": {
- // Run desktop+mobile in parallel (needs more resources). If false, runs sequentially.
- "parallel": false,
- // If false and there are 0 points available, the run is skipped early to save time.
- "runOnZeroPoints": false,
- // Number of account clusters (processes) to run concurrently.
- "clusters": 1,
- // Number of passes per invocation (advanced; usually 1).
- "passesPerRun": 1
- },
-
- "buyMode": {
- // Manual purchase/redeem mode. Use CLI -buy to enable, or set buyMode.enabled in config.
- // Session duration cap in minutes.
- "maxMinutes": 45
- },
-
"fingerprinting": {
- // Persist browser fingerprints per device type to improve consistency across runs
+ // Persist browser fingerprints to improve consistency across runs
"saveFingerprint": {
"mobile": true,
"desktop": true
}
},
+
+ // ============================================================
+ // ⚙️ EXECUTION & PERFORMANCE
+ // ============================================================
+
+ "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,
+ // Number of passes per invocation (usually 1)
+ "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": false
+ },
+
+ "jobState": {
+ // Save state to avoid duplicate work across restarts
+ "enabled": true,
+ // Custom state directory (empty = defaults to sessionPath/job-state)
+ "dir": ""
+ },
+
+
+ // ============================================================
+ // 🎯 TASKS & WORKERS
+ // ============================================================
+
+ "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
+ "bundleDailySetWithSearch": true
+ },
+
+
+ // ============================================================
+ // 🔍 SEARCH CONFIGURATION
+ // ============================================================
+
"search": {
// Use locale-specific query sources
"useLocalQueries": true,
"settings": {
- // Add geo/locale signal into query selection
+ // Use region-specific queries (at, fr, us, etc.)
"useGeoLocaleQueries": true,
- // Randomly scroll search result pages to look more natural
+ // Randomly scroll search result pages (more natural behavior)
"scrollRandomResults": true,
// Occasionally click a result (safe targets only)
"clickRandomResults": true,
- // Number of times to retry mobile searches if points didn’t progress
+ // Number of retries if mobile searches don't progress
"retryMobileSearchAmount": 2,
- // Delay between searches (supports numbers in ms or time strings)
+ // Delay between searches
"delay": {
"min": "1min",
"max": "5min"
@@ -56,38 +114,70 @@
}
},
- "humanization": {
- // Global Human Mode switch. true=adds subtle micro-gestures & pauses. false=classic behavior.
+ "queryDiversity": {
+ // Multi-source query generation: Reddit, News, Wikipedia instead of only Google Trends
"enabled": true,
- // If true, as soon as a ban is detected on any account, stop processing remaining accounts
- // (ban detection is based on centralized heuristics and error signals)
+ // 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": {
+ // 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,
- // If true, immediately send an alert (webhook/NTFY) when a ban is detected
+ // Immediately send an alert (webhook/NTFY) when a ban is detected
"immediateBanAlert": true,
- // Extra random pause between actions (ms or time string e.g., "300ms", "1s")
+ // Extra random pause between actions
"actionDelay": {
- "min": 500,
- "max": 2200
+ "min": 500, // 0.5 seconds minimum
+ "max": 2200 // 2.2 seconds maximum
},
- // Probability (0..1) to move mouse a tiny bit in between actions
+ // Probability (0-1) to move mouse slightly between actions
"gestureMoveProb": 0.65,
- // Probability (0..1) to perform a very small scroll
+ // Probability (0-1) to perform a small scroll
"gestureScrollProb": 0.4,
- // Optional local-time windows for execution (e.g., ["08:30-11:00", "19:00-22:00"]).
- // If provided, runs will wait until inside a window before starting.
+ // Optional execution time windows (e.g., ["08:30-11:00", "19:00-22:00"])
+ // If specified, waits until inside a window before starting
"allowedWindows": []
},
-
- // Optional monthly "vacation" block: skip a contiguous range of days to look more human.
- // This is independent of weekly random off-days. When enabled, each month a random
- // block between minDays and maxDays is selected (e.g., 2–4 days) and all runs within
- // that date range are skipped. The chosen block is logged at the start of the month.
+
"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
+ // ============================================================
+
+ "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,
@@ -97,18 +187,10 @@
"jitter": 0.2
},
- "workers": {
- // Select what the bot should complete on desktop/mobile
- "doDailySet": true,
- "doMorePromotions": true,
- "doPunchCards": true,
- "doDesktopSearch": true,
- "doMobileSearch": true,
- "doDailyCheckIn": true,
- "doReadToEarn": true,
- // If true, run a desktop search bundle right after Daily Set
- "bundleDailySetWithSearch": true
- },
+
+ // ============================================================
+ // 🌐 PROXY
+ // ============================================================
"proxy": {
// Control which outbound calls go through your proxy
@@ -116,13 +198,18 @@
"proxyBingTerms": true
},
+
+ // ============================================================
+ // 🔔 NOTIFICATIONS
+ // ============================================================
+
"notifications": {
- // Live logs (Discord or similar). URL is your webhook endpoint.
+ // Live logs webhook (Discord or similar). URL = your webhook endpoint
"webhook": {
"enabled": false,
"url": ""
},
- // Rich end-of-run summary (Discord or similar)
+ // Rich end-of-run summary webhook (Discord or similar)
"conclusionWebhook": {
"enabled": false,
"url": ""
@@ -136,9 +223,14 @@
}
},
+
+ // ============================================================
+ // 📊 LOGGING & DIAGNOSTICS
+ // ============================================================
+
"logging": {
- // Logging controls (see docs/config.md). Remove redactEmails or set false to show full emails.
- // Filter out noisy log buckets locally and for any webhook summaries
+ // Logging controls (see docs/config.md)
+ // Filter out noisy log buckets locally and for webhook summaries
"excludeFunc": [
"SEARCH-CLOSE-TABS",
"LOGIN-NO-PROMPT",
@@ -149,12 +241,12 @@
"LOGIN-NO-PROMPT",
"FLOW"
],
- // Email redaction toggle (previously logging.live.redactEmails)
+ // Email redaction toggle (true = secure, false = full emails)
"redactEmails": true
},
"diagnostics": {
- // Capture minimal evidence on failures (screenshots/HTML) and prune old runs
+ // Capture minimal evidence on failures (screenshots/HTML)
"enabled": true,
"saveScreenshot": true,
"saveHtml": true,
@@ -162,75 +254,45 @@
"retentionDays": 7
},
-
-
- "jobState": {
- // Checkpoint to avoid duplicate work across restarts
- "enabled": true,
- // Custom state directory (defaults to sessionPath/job-state if empty)
- "dir": ""
- },
-
- "schedule": {
- // Built-in scheduler (no cron needed in container). Uses the IANA time zone below.
- "enabled": false,
- // Choose YOUR preferred time format:
- // - US style with AM/PM → set useAmPm: true and edit time12 (e.g., "9:00 AM")
- // - 24-hour style → set useAmPm: false and edit time24 (e.g., "09:00")
- // Back-compat: if both time12/time24 are empty, the legacy "time" (HH:mm) will be used if present.
- "useAmPm": false,
- "time12": "9:00 AM",
- "time24": "09:00",
- // IANA timezone for scheduling (set to your region), e.g. "Europe/Paris" or "America/New_York"
- "timeZone": "America/New_York",
- // If true, run one pass immediately when the process starts
- "runImmediatelyOnStart": false
- },
-
- "update": {
- // Optional post-run auto-update
- "git": true,
- "docker": false,
- // Custom updater script path (relative to repo root)
- "scriptPath": "setup/update/update.mjs"
- },
-
- // NEW INTELLIGENT FEATURES
- "riskManagement": {
- // Risk-Aware Throttling: dynamically adjusts delays based on detected risk signals
- "enabled": true,
- // Automatically increase delays when captchas/errors are detected
- "autoAdjustDelays": true,
- // Stop execution if risk level reaches critical (score > riskThreshold)
- "stopOnCritical": false,
- // Enable ML-style ban prediction based on patterns
- "banPrediction": true,
- // Risk threshold (0-100). If exceeded, bot pauses or alerts you.
- "riskThreshold": 75
- },
-
"analytics": {
- // Performance Dashboard: track points earned, success rates, execution times
+ // 📈 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": false
+ "webhookSummary": true
},
- "queryDiversity": {
- // Multi-source query generation: use Reddit, News, Wikipedia instead of just Google Trends
- "enabled": true,
- // Which sources to use (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
+
+ // ============================================================
+ // 🛒 BUY MODE
+ // ============================================================
+
+ "buyMode": {
+ // Manual purchase/redeem mode. Use CLI -buy to enable
+ // Session duration cap in minutes
+ "maxMinutes": 45
},
- // Dry-run mode: simulate execution without actually running tasks (useful for testing config)
- "dryRun": false
+
+ // ============================================================
+ // 🔄 UPDATES
+ // ============================================================
+
+ "update": {
+ // Post-run auto-update settings
+ "git": true,
+ "docker": false,
+ // Custom updater script path (relative to repo root)
+ "scriptPath": "setup/update/update.mjs"
+ }
}
diff --git a/src/constants.ts b/src/constants.ts
new file mode 100644
index 0000000..3a80f8f
--- /dev/null
+++ b/src/constants.ts
@@ -0,0 +1,67 @@
+/**
+ * Central constants file for the Microsoft Rewards Script
+ * Defines timeouts, retry limits, and other magic numbers used throughout the application
+ */
+
+export const TIMEOUTS = {
+ SHORT: 500,
+ MEDIUM: 1500,
+ MEDIUM_LONG: 2000,
+ LONG: 3000,
+ VERY_LONG: 5000,
+ EXTRA_LONG: 10000,
+ DASHBOARD_WAIT: 10000,
+ LOGIN_MAX: 180000, // 3 minutes
+ NETWORK_IDLE: 5000
+} as const
+
+export const RETRY_LIMITS = {
+ MAX_ITERATIONS: 5,
+ DASHBOARD_RELOAD: 2,
+ MOBILE_SEARCH: 3,
+ ABC_MAX: 15,
+ POLL_MAX: 15,
+ QUIZ_MAX: 15,
+ QUIZ_ANSWER_TIMEOUT: 10000,
+ GO_HOME_MAX: 5
+} as const
+
+export const DELAYS = {
+ ACTION_MIN: 1000,
+ ACTION_MAX: 3000,
+ SEARCH_DEFAULT_MIN: 2000,
+ SEARCH_DEFAULT_MAX: 5000,
+ BROWSER_CLOSE: 2000,
+ TYPING_DELAY: 20,
+ SEARCH_ON_BING_WAIT: 5000,
+ SEARCH_ON_BING_COMPLETE: 3000,
+ SEARCH_ON_BING_FOCUS: 200,
+ SEARCH_BAR_TIMEOUT: 15000,
+ QUIZ_ANSWER_WAIT: 2000,
+ THIS_OR_THAT_START: 2000
+} as const
+
+export const SELECTORS = {
+ MORE_ACTIVITIES: '#more-activities',
+ SUSPENDED_ACCOUNT: '#suspendedAccountHeader',
+ QUIZ_COMPLETE: '#quizCompleteContainer',
+ QUIZ_CREDITS: 'span.rqMCredits'
+} as const
+
+export const URLS = {
+ REWARDS_BASE: 'https://rewards.bing.com',
+ REWARDS_SIGNIN: 'https://rewards.bing.com/signin',
+ APP_USER_DATA: 'https://prod.rewardsplatform.microsoft.com/dapi/me?channel=SAAndroid&options=613'
+} as const
+
+export const DISCORD = {
+ MAX_EMBED_LENGTH: 1900,
+ RATE_LIMIT_DELAY: 500,
+ WEBHOOK_TIMEOUT: 10000,
+ DEBOUNCE_DELAY: 750,
+ COLOR_RED: 0xFF0000,
+ COLOR_CRIMSON: 0xDC143C,
+ COLOR_ORANGE: 0xFFA500,
+ COLOR_BLUE: 0x3498DB,
+ COLOR_GREEN: 0x00D26A
+} as const
diff --git a/src/functions/Login.ts b/src/functions/Login.ts
index 1e3d2fd..c576cad 100644
--- a/src/functions/Login.ts
+++ b/src/functions/Login.ts
@@ -202,6 +202,10 @@ export class Login {
// --------------- 2FA Handling ---------------
private async handle2FA(page: Page) {
try {
+ // Dismiss any popups/dialogs before checking 2FA (Terms Update, etc.)
+ await this.bot.browser.utils.tryDismissAllMessages(page)
+ await this.bot.utils.wait(500)
+
if (this.currentTotpSecret) {
const totpSelector = await this.ensureTotpInput(page)
if (totpSelector) {
@@ -273,10 +277,45 @@ export class Login {
} catch {/* ignore */}
}
- // Manual prompt
+ // Manual prompt with periodic page check
this.bot.log(this.bot.isMobile, 'LOGIN', 'Waiting for user 2FA code (SMS / Email / App fallback)')
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
- const code: string = await new Promise(res => rl.question('Enter 2FA code:\n', ans => { rl.close(); res(ans.trim()) }))
+
+ // Monitor page changes while waiting for user input
+ let userInput: string | null = null
+ let checkInterval: NodeJS.Timeout | null = null
+
+ const inputPromise = new Promise(res => {
+ rl.question('Enter 2FA code:\n', ans => {
+ if (checkInterval) clearInterval(checkInterval)
+ rl.close()
+ res(ans.trim())
+ })
+ })
+
+ // Check every 2 seconds if user manually progressed past the dialog
+ checkInterval = setInterval(async () => {
+ try {
+ await this.bot.browser.utils.tryDismissAllMessages(page)
+ // Check if we're no longer on 2FA page
+ const still2FA = await page.locator('input[name="otc"]').first().isVisible({ timeout: 500 }).catch(() => false)
+ if (!still2FA) {
+ this.bot.log(this.bot.isMobile, 'LOGIN', 'Page changed during 2FA wait (user may have clicked Next)', 'warn')
+ if (checkInterval) clearInterval(checkInterval)
+ rl.close()
+ userInput = 'skip' // Signal to skip submission
+ }
+ } catch {/* ignore */}
+ }, 2000)
+
+ const code = await inputPromise
+ if (checkInterval) clearInterval(checkInterval)
+
+ if (code === 'skip' || userInput === 'skip') {
+ this.bot.log(this.bot.isMobile, 'LOGIN', 'Skipping 2FA code submission (page progressed)')
+ return
+ }
+
await page.fill('input[name="otc"]', code)
await page.keyboard.press('Enter')
this.bot.log(this.bot.isMobile, 'LOGIN', '2FA code submitted')
@@ -729,7 +768,7 @@ export class Login {
}
private getDocsUrl(anchor?: string) {
- const base = process.env.DOCS_BASE?.trim() || 'https://github.com/LightZirconite/Microsoft-Rewards-Script-Private/blob/V2/docs/security.md'
+ const base = process.env.DOCS_BASE?.trim() || 'https://github.com/LightZirconite/Microsoft-Rewards-Script-Private/blob/v2/docs/security.md'
const map: Record = {
'recovery-email-mismatch':'#recovery-email-mismatch',
'we-cant-sign-you-in':'#we-cant-sign-you-in-blocked'
diff --git a/src/functions/activities/ABC.ts b/src/functions/activities/ABC.ts
index 72e61d7..e1827fe 100644
--- a/src/functions/activities/ABC.ts
+++ b/src/functions/activities/ABC.ts
@@ -1,6 +1,7 @@
import { Page } from 'rebrowser-playwright'
import { Workers } from '../Workers'
+import { RETRY_LIMITS, TIMEOUTS } from '../../constants'
export class ABC extends Workers {
@@ -11,34 +12,32 @@ export class ABC extends Workers {
try {
let $ = await this.bot.browser.func.loadInCheerio(page)
- // Don't loop more than 15 in case unable to solve, would lock otherwise
- const maxIterations = 15
let i
- for (i = 0; i < maxIterations && !$('span.rw_icon').length; i++) {
- await page.waitForSelector('.wk_OptionClickClass', { state: 'visible', timeout: 10000 })
+ for (i = 0; i < RETRY_LIMITS.ABC_MAX && !$('span.rw_icon').length; i++) {
+ await page.waitForSelector('.wk_OptionClickClass', { state: 'visible', timeout: TIMEOUTS.DASHBOARD_WAIT })
const answers = $('.wk_OptionClickClass')
const answer = answers[this.bot.utils.randomNumber(0, 2)]?.attribs['id']
- await page.waitForSelector(`#${answer}`, { state: 'visible', timeout: 10000 })
+ await page.waitForSelector(`#${answer}`, { state: 'visible', timeout: TIMEOUTS.DASHBOARD_WAIT })
- await this.bot.utils.wait(2000)
+ await this.bot.utils.wait(TIMEOUTS.MEDIUM_LONG)
await page.click(`#${answer}`) // Click answer
- await this.bot.utils.wait(4000)
- await page.waitForSelector('div.wk_button', { state: 'visible', timeout: 10000 })
+ await this.bot.utils.wait(TIMEOUTS.LONG + 1000)
+ await page.waitForSelector('div.wk_button', { state: 'visible', timeout: TIMEOUTS.DASHBOARD_WAIT })
await page.click('div.wk_button') // Click next question button
page = await this.bot.browser.utils.getLatestTab(page)
$ = await this.bot.browser.func.loadInCheerio(page)
- await this.bot.utils.wait(1000)
+ await this.bot.utils.wait(TIMEOUTS.MEDIUM)
}
- await this.bot.utils.wait(4000)
+ await this.bot.utils.wait(TIMEOUTS.LONG + 1000)
await page.close()
- if (i === maxIterations) {
- this.bot.log(this.bot.isMobile, 'ABC', 'Failed to solve quiz, exceeded max iterations of 15', 'warn')
+ if (i === RETRY_LIMITS.ABC_MAX) {
+ this.bot.log(this.bot.isMobile, 'ABC', `Failed to solve quiz, exceeded max iterations of ${RETRY_LIMITS.ABC_MAX}`, 'warn')
} else {
this.bot.log(this.bot.isMobile, 'ABC', 'Completed the ABC successfully')
}
diff --git a/src/functions/activities/Poll.ts b/src/functions/activities/Poll.ts
index 526a2e0..57b4ec5 100644
--- a/src/functions/activities/Poll.ts
+++ b/src/functions/activities/Poll.ts
@@ -1,6 +1,7 @@
import { Page } from 'rebrowser-playwright'
import { Workers } from '../Workers'
+import { TIMEOUTS } from '../../constants'
export class Poll extends Workers {
@@ -11,12 +12,14 @@ export class Poll extends Workers {
try {
const buttonId = `#btoption${Math.floor(this.bot.utils.randomNumber(0, 1))}`
- await page.waitForSelector(buttonId, { state: 'visible', timeout: 10000 }).catch(() => { }) // We're gonna click regardless or not
- await this.bot.utils.wait(2000)
+ await page.waitForSelector(buttonId, { state: 'visible', timeout: TIMEOUTS.DASHBOARD_WAIT }).catch((e) => {
+ this.bot.log(this.bot.isMobile, 'POLL', `Could not find poll button: ${e}`, 'warn')
+ })
+ await this.bot.utils.wait(TIMEOUTS.MEDIUM_LONG)
await page.click(buttonId)
- await this.bot.utils.wait(4000)
+ await this.bot.utils.wait(TIMEOUTS.LONG + 1000)
await page.close()
this.bot.log(this.bot.isMobile, 'POLL', 'Completed the poll successfully')
diff --git a/src/functions/activities/Quiz.ts b/src/functions/activities/Quiz.ts
index c858024..4bbe619 100644
--- a/src/functions/activities/Quiz.ts
+++ b/src/functions/activities/Quiz.ts
@@ -1,6 +1,7 @@
import { Page } from 'rebrowser-playwright'
import { Workers } from '../Workers'
+import { RETRY_LIMITS, TIMEOUTS, DELAYS } from '../../constants'
export class Quiz extends Workers {
@@ -10,19 +11,19 @@ export class Quiz extends Workers {
try {
// Check if the quiz has been started or not
- const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { state: 'visible', timeout: 2000 }).then(() => true).catch(() => false)
+ const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { state: 'visible', timeout: TIMEOUTS.MEDIUM_LONG }).then(() => true).catch(() => false)
if (quizNotStarted) {
await page.click('#rqStartQuiz')
} else {
this.bot.log(this.bot.isMobile, 'QUIZ', 'Quiz has already been started, trying to finish it')
}
- await this.bot.utils.wait(2000)
+ await this.bot.utils.wait(TIMEOUTS.MEDIUM_LONG)
let quizData = await this.bot.browser.func.getQuizData(page)
// Verify quiz is actually loaded before proceeding
- const firstOptionExists = await page.waitForSelector('#rqAnswerOption0', { state: 'attached', timeout: 5000 }).then(() => true).catch(() => false)
+ const firstOptionExists = await page.waitForSelector('#rqAnswerOption0', { state: 'attached', timeout: TIMEOUTS.VERY_LONG }).then(() => true).catch(() => false)
if (!firstOptionExists) {
this.bot.log(this.bot.isMobile, 'QUIZ', 'Quiz options not found - page may not have loaded correctly. Skipping.', 'warn')
await page.close()
@@ -37,7 +38,7 @@ export class Quiz extends Workers {
const answers: string[] = []
for (let i = 0; i < quizData.numberOfOptions; i++) {
- const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10000 }).catch(() => null)
+ const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: TIMEOUTS.DASHBOARD_WAIT }).catch(() => null)
if (!answerSelector) {
this.bot.log(this.bot.isMobile, 'QUIZ', `Option ${i} not found - quiz structure may have changed. Skipping remaining options.`, 'warn')
@@ -60,7 +61,7 @@ export class Quiz extends Workers {
// Click the answers
for (const answer of answers) {
- await page.waitForSelector(answer, { state: 'visible', timeout: 2000 })
+ await page.waitForSelector(answer, { state: 'visible', timeout: DELAYS.QUIZ_ANSWER_WAIT })
// Click the answer on page
await page.click(answer)
@@ -82,7 +83,7 @@ export class Quiz extends Workers {
for (let i = 0; i < quizData.numberOfOptions; i++) {
- const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10000 }).catch(() => null)
+ const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: RETRY_LIMITS.QUIZ_ANSWER_TIMEOUT }).catch(() => null)
if (!answerSelector) {
this.bot.log(this.bot.isMobile, 'QUIZ', `Option ${i} not found for ${quizData.numberOfOptions}-option quiz. Skipping.`, 'warn')
@@ -112,12 +113,12 @@ export class Quiz extends Workers {
return
}
- await this.bot.utils.wait(2000)
+ await this.bot.utils.wait(DELAYS.QUIZ_ANSWER_WAIT)
}
}
// Done with
- await this.bot.utils.wait(2000)
+ await this.bot.utils.wait(DELAYS.QUIZ_ANSWER_WAIT)
await page.close()
this.bot.log(this.bot.isMobile, 'QUIZ', 'Completed the quiz successfully')
diff --git a/src/functions/activities/Search.ts b/src/functions/activities/Search.ts
index e65107d..593b717 100644
--- a/src/functions/activities/Search.ts
+++ b/src/functions/activities/Search.ts
@@ -277,8 +277,10 @@ export class Search extends Workers {
}
const mappedTrendsData = trendsData.map(query => [query[0], query[9]!.slice(1)])
- if (mappedTrendsData.length < 90) {
- this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', 'Insufficient search queries, falling back to US', 'warn')
+ this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', `Found ${mappedTrendsData.length} search queries for ${geoLocale}`)
+
+ if (mappedTrendsData.length < 30 && geoLocale.toUpperCase() !== 'US') {
+ this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', `Insufficient search queries (${mappedTrendsData.length} < 30), falling back to US`, 'warn')
return this.getGoogleTrends()
}
diff --git a/src/functions/activities/SearchOnBing.ts b/src/functions/activities/SearchOnBing.ts
index 561d49b..aa0f999 100644
--- a/src/functions/activities/SearchOnBing.ts
+++ b/src/functions/activities/SearchOnBing.ts
@@ -3,6 +3,7 @@ import * as fs from 'fs'
import path from 'path'
import { Workers } from '../Workers'
+import { DELAYS } from '../../constants'
import { MorePromotion, PromotionalItem } from '../../interface/DashboardData'
@@ -13,7 +14,7 @@ export class SearchOnBing extends Workers {
this.bot.log(this.bot.isMobile, 'SEARCH-ON-BING', 'Trying to complete SearchOnBing')
try {
- await this.bot.utils.wait(5000)
+ await this.bot.utils.wait(DELAYS.SEARCH_ON_BING_WAIT)
await this.bot.browser.utils.tryDismissAllMessages(page)
@@ -21,20 +22,20 @@ export class SearchOnBing extends Workers {
const searchBar = '#sb_form_q'
const box = page.locator(searchBar)
- await box.waitFor({ state: 'attached', timeout: 15000 })
+ await box.waitFor({ state: 'attached', timeout: DELAYS.SEARCH_BAR_TIMEOUT })
await this.bot.browser.utils.tryDismissAllMessages(page)
- await this.bot.utils.wait(200)
+ await this.bot.utils.wait(DELAYS.SEARCH_ON_BING_FOCUS)
try {
- await box.focus({ timeout: 2000 }).catch(() => { /* ignore */ })
+ await box.focus({ timeout: DELAYS.THIS_OR_THAT_START }).catch(() => { /* ignore */ })
await box.fill('')
- await this.bot.utils.wait(200)
- await page.keyboard.type(query, { delay: 20 })
+ await this.bot.utils.wait(DELAYS.SEARCH_ON_BING_FOCUS)
+ await page.keyboard.type(query, { delay: DELAYS.TYPING_DELAY })
await page.keyboard.press('Enter')
} catch {
const url = `https://www.bing.com/search?q=${encodeURIComponent(query)}`
await page.goto(url)
}
- await this.bot.utils.wait(3000)
+ await this.bot.utils.wait(DELAYS.SEARCH_ON_BING_COMPLETE)
await page.close()
diff --git a/src/functions/activities/ThisOrThat.ts b/src/functions/activities/ThisOrThat.ts
index f6e6edb..5cf7b0f 100644
--- a/src/functions/activities/ThisOrThat.ts
+++ b/src/functions/activities/ThisOrThat.ts
@@ -1,6 +1,7 @@
import { Page } from 'rebrowser-playwright'
import { Workers } from '../Workers'
+import { DELAYS } from '../../constants'
export class ThisOrThat extends Workers {
@@ -11,14 +12,14 @@ export class ThisOrThat extends Workers {
try {
// Check if the quiz has been started or not
- const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { state: 'visible', timeout: 2000 }).then(() => true).catch(() => false)
+ const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { state: 'visible', timeout: DELAYS.THIS_OR_THAT_START }).then(() => true).catch(() => false)
if (quizNotStarted) {
await page.click('#rqStartQuiz')
} else {
this.bot.log(this.bot.isMobile, 'THIS-OR-THAT', 'ThisOrThat has already been started, trying to finish it')
}
- await this.bot.utils.wait(2000)
+ await this.bot.utils.wait(DELAYS.THIS_OR_THAT_START)
// Solving
const quizData = await this.bot.browser.func.getQuizData(page)
diff --git a/src/functions/activities/UrlReward.ts b/src/functions/activities/UrlReward.ts
index b5c310e..cbc94f4 100644
--- a/src/functions/activities/UrlReward.ts
+++ b/src/functions/activities/UrlReward.ts
@@ -9,7 +9,7 @@ export class UrlReward extends Workers {
this.bot.log(this.bot.isMobile, 'URL-REWARD', 'Trying to complete UrlReward')
try {
- this.bot.utils.wait(2000)
+ await this.bot.utils.wait(2000)
await page.close()
diff --git a/src/index.ts b/src/index.ts
index 1b0b6b6..efdbad7 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -10,6 +10,7 @@ import BrowserUtil from './browser/BrowserUtil'
import { log } from './util/Logger'
import Util from './util/Utils'
import { loadAccounts, loadConfig, saveSessionData } from './util/Load'
+import { DISCORD } from './constants'
import { Login } from './functions/Login'
import { Workers } from './functions/Workers'
@@ -66,8 +67,7 @@ export class MicrosoftRewardsBot {
private heartbeatFile?: string
private heartbeatTimer?: NodeJS.Timeout
- //@ts-expect-error Will be initialized later
- public axios: Axios
+ public axios!: Axios
constructor(isMobile: boolean) {
this.isMobile = isMobile
@@ -650,10 +650,15 @@ export class MicrosoftRewardsBot {
// Cleanup heartbeat timer/file at end of run
if (this.heartbeatTimer) { try { clearInterval(this.heartbeatTimer) } catch { /* ignore */ } }
if (this.heartbeatFile) { try { if (fs.existsSync(this.heartbeatFile)) fs.unlinkSync(this.heartbeatFile) } catch { /* ignore */ } }
- // After conclusion, run optional auto-update
- await this.runAutoUpdate().catch(() => {/* ignore update errors */})
+ // After conclusion, run optional auto-update (only if not in scheduler mode)
+ if (!process.env.SCHEDULER_HEARTBEAT_FILE) {
+ await this.runAutoUpdate().catch(() => {/* ignore update errors */})
+ }
+ }
+ // Only exit if not spawned by scheduler
+ if (!process.env.SCHEDULER_HEARTBEAT_FILE) {
+ process.exit()
}
- process.exit()
}
/** Send immediate ban alert if configured. */
@@ -669,7 +674,7 @@ export class MicrosoftRewardsBot {
{
title,
description: desc,
- color: 0xFF0000
+ color: DISCORD.COLOR_RED
}
]
})
@@ -956,20 +961,7 @@ export class MicrosoftRewardsBot {
let accountsWithErrors = 0
let successes = 0
- type DiscordField = { name: string; value: string; inline?: boolean }
- type DiscordFooter = { text: string }
- type DiscordEmbed = {
- title?: string
- description?: string
- color?: number
- fields?: DiscordField[]
- timestamp?: string
- footer?: DiscordFooter
- }
-
- const accountFields: DiscordField[] = []
- const accountLines: string[] = []
-
+ // Calculate summary statistics
for (const s of summaries) {
totalCollected += s.totalCollected
totalInitial += s.initialTotal
@@ -977,44 +969,12 @@ export class MicrosoftRewardsBot {
totalDuration += s.durationMs
if (s.errors.length) accountsWithErrors++
else successes++
-
- const statusEmoji = s.banned?.status ? '🚫' : (s.errors.length ? '⚠️' : '✅')
- const diff = s.totalCollected
- const duration = formatDuration(s.durationMs)
-
- // Build embed fields (Discord)
- const valueLines: string[] = [
- `Points: ${s.initialTotal} → ${s.endTotal} ( +${diff} )`,
- `Breakdown: 🖥️ ${s.desktopCollected} | 📱 ${s.mobileCollected}`,
- `Duration: ⏱️ ${duration}`
- ]
- if (s.banned?.status) {
- valueLines.push(`Banned: ${s.banned.reason || 'detected by heuristics'}`)
- }
- if (s.errors.length) {
- valueLines.push(`Errors: ${s.errors.slice(0, 2).join(' | ')}`)
- }
- accountFields.push({
- name: `${statusEmoji} ${s.email}`.substring(0, 256),
- value: valueLines.join('\n').substring(0, 1024),
- inline: false
- })
-
- // Build plain text lines (NTFY)
- const lines = [
- `${statusEmoji} ${s.email}`,
- ` Points: ${s.initialTotal} → ${s.endTotal} ( +${diff} )`,
- ` 🖥️ ${s.desktopCollected} | 📱 ${s.mobileCollected}`,
- ` Duration: ${duration}`
- ]
- if (s.banned?.status) lines.push(` Banned: ${s.banned.reason || 'detected by heuristics'}`)
- if (s.errors.length) lines.push(` Errors: ${s.errors.slice(0, 2).join(' | ')}`)
- accountLines.push(lines.join('\n') + '\n')
}
const avgDuration = totalDuration / totalAccounts
+ const avgPointsPerAccount = Math.round(totalCollected / totalAccounts)
- // Read package version (best-effort)
+ // Read package version
let version = 'unknown'
try {
const pkgPath = path.join(process.cwd(), 'package.json')
@@ -1025,80 +985,70 @@ export class MicrosoftRewardsBot {
}
} catch { /* ignore */ }
- // Discord/Webhook embeds with chunking (limits: 10 embeds/message, 25 fields/embed)
- const MAX_EMBEDS = 10
- const MAX_FIELDS = 25
+ // Build clean embed with account details
+ type DiscordField = { name: string; value: string; inline?: boolean }
+ type DiscordEmbed = {
+ title?: string
+ description?: string
+ color?: number
+ fields?: DiscordField[]
+ thumbnail?: { url: string }
+ timestamp?: string
+ footer?: { text: string; icon_url?: string }
+ }
- const baseFields = [
- {
- name: 'Global Totals',
- value: [
- `Total Points: ${totalInitial} → ${totalEnd} ( +${totalCollected} )`,
- `Accounts: ✅ ${successes} • ⚠️ ${accountsWithErrors} (of ${totalAccounts})`,
- `Average Duration: ${formatDuration(avgDuration)}`,
- `Cumulative Runtime: ${formatDuration(totalDuration)}`
- ].join('\n')
- }
- ]
+ const accountDetails: string[] = []
+ for (const s of summaries) {
+ const statusIcon = s.banned?.status ? '🚫' : (s.errors.length ? '⚠️' : '✅')
+ const line = `${statusIcon} **${s.email}** → +${s.totalCollected}pts (🖥️${s.desktopCollected} 📱${s.mobileCollected}) • ${formatDuration(s.durationMs)}`
+ accountDetails.push(line)
+ if (s.banned?.status) accountDetails.push(` └ Banned: ${s.banned.reason || 'detected'}`)
+ if (s.errors.length > 0) accountDetails.push(` └ Errors: ${s.errors.slice(0, 2).join(', ')}`)
+ }
- // Prepare embeds: first embed for totals, subsequent for accounts
- const embeds: DiscordEmbed[] = []
- const headerEmbed: DiscordEmbed = {
- title: '🎯 Microsoft Rewards Summary',
- description: `Processed **${totalAccounts}** account(s)${accountsWithErrors ? ` • ${accountsWithErrors} with issues` : ''}`,
- color: accountsWithErrors ? 0xFFAA00 : 0x32CD32,
- fields: baseFields,
+ const embed: DiscordEmbed = {
+ title: '🎯 Microsoft Rewards - Daily Summary',
+ description: [
+ '**📊 Global Statistics**',
+ `├ Total Points: **${totalInitial}** → **${totalEnd}** (+**${totalCollected}**)`,
+ `├ Accounts: ✅ ${successes} • ${accountsWithErrors > 0 ? `⚠️ ${accountsWithErrors}` : ''} (${totalAccounts} total)`,
+ `├ Average: **${avgPointsPerAccount}pts/account** • **${formatDuration(avgDuration)}/account**`,
+ `└ Runtime: **${formatDuration(totalDuration)}**`,
+ '',
+ '**📈 Account Details**',
+ ...accountDetails
+ ].filter(Boolean).join('\n'),
+ color: accountsWithErrors > 0 ? DISCORD.COLOR_ORANGE : DISCORD.COLOR_GREEN,
+ thumbnail: {
+ url: 'https://media.discordapp.net/attachments/1421163952972369931/1421929950377939125/Gc.png'
+ },
timestamp: new Date().toISOString(),
- footer: { text: `Run ${this.runId}${version !== 'unknown' ? ` • v${version}` : ''}` }
- }
- embeds.push(headerEmbed)
-
- // Chunk account fields across remaining embeds
- const fieldsPerEmbed = Math.min(MAX_FIELDS, 25)
- const availableEmbeds = MAX_EMBEDS - embeds.length
- const chunks: DiscordField[][] = []
- for (let i = 0; i < accountFields.length; i += fieldsPerEmbed) {
- chunks.push(accountFields.slice(i, i + fieldsPerEmbed))
- }
-
- const includedChunks = chunks.slice(0, availableEmbeds)
- for (const [idx, chunk] of includedChunks.entries()) {
- const chunkEmbed: DiscordEmbed = {
- title: `Accounts ${idx * fieldsPerEmbed + 1}–${Math.min((idx + 1) * fieldsPerEmbed, accountFields.length)}`,
- color: accountsWithErrors ? 0xFFAA00 : 0x32CD32,
- fields: chunk,
- timestamp: new Date().toISOString()
- }
- embeds.push(chunkEmbed)
- }
-
- const omitted = chunks.length - includedChunks.length
- if (omitted > 0 && embeds.length > 0) {
- // Add a small note to the last embed about omitted accounts
- const last = embeds[embeds.length - 1]!
- const noteField: DiscordField = { name: 'Note', value: `And ${omitted * fieldsPerEmbed} more account entries not shown due to Discord limits.`, inline: false }
- if (last.fields && Array.isArray(last.fields)) {
- last.fields = [...last.fields, noteField].slice(0, MAX_FIELDS)
+ footer: {
+ text: `MS Rewards Bot v${version} • Run ${this.runId}`,
+ icon_url: 'https://media.discordapp.net/attachments/1421163952972369931/1421929950377939125/Gc.png'
}
}
- // NTFY-compatible plain text (includes per-account breakdown)
+ // NTFY plain text fallback
const fallback = [
- 'Microsoft Rewards Summary',
- `Accounts: ${totalAccounts}${accountsWithErrors ? ` • ${accountsWithErrors} with issues` : ''}`,
- `Total: ${totalInitial} -> ${totalEnd} (+${totalCollected})`,
- `Average Duration: ${formatDuration(avgDuration)}`,
- `Cumulative Runtime: ${formatDuration(totalDuration)}`,
+ '🎯 Microsoft Rewards Summary',
+ `Accounts: ${totalAccounts} (✅${successes} ${accountsWithErrors > 0 ? `⚠️${accountsWithErrors}` : ''})`,
+ `Total: ${totalInitial}→${totalEnd} (+${totalCollected})`,
+ `Average: ${avgPointsPerAccount}pts/account • ${formatDuration(avgDuration)}`,
+ `Runtime: ${formatDuration(totalDuration)}`,
'',
- ...accountLines
+ ...summaries.map(s => {
+ const st = s.banned?.status ? '🚫' : (s.errors.length ? '⚠️' : '✅')
+ return `${st} ${s.email}: +${s.totalCollected}pts (🖥️${s.desktopCollected} 📱${s.mobileCollected})`
+ })
].join('\n')
- // Send both when any channel is enabled: Discord gets embeds, NTFY gets fallback
- if (conclusionWebhookEnabled || ntfyEnabled || webhookEnabled) {
- await ConclusionWebhook(cfg, fallback, { embeds })
- }
+ // Send webhook
+ if (conclusionWebhookEnabled || ntfyEnabled || webhookEnabled) {
+ await ConclusionWebhook(cfg, fallback, { embeds: [embed] })
+ }
- // Write local JSON report for observability
+ // Write local JSON report
try {
const fs = await import('fs')
const path = await import('path')
@@ -1119,7 +1069,7 @@ export class MicrosoftRewardsBot {
log('main','REPORT',`Failed to save report: ${e instanceof Error ? e.message : e}`,'warn')
}
- // Optionally cleanup old diagnostics folders
+ // Cleanup old diagnostics
try {
const days = cfg.diagnostics?.retentionDays
if (typeof days === 'number' && days > 0) {
@@ -1128,6 +1078,7 @@ export class MicrosoftRewardsBot {
} catch (e) {
log('main','REPORT',`Failed diagnostics cleanup: ${e instanceof Error ? e.message : e}`,'warn')
}
+
}
/** Reserve one diagnostics slot for this run (caps captures). */
@@ -1209,7 +1160,7 @@ export class MicrosoftRewardsBot {
{
title,
description: desc,
- color: 0xFF0000
+ color: DISCORD.COLOR_RED
}
]
})
@@ -1252,8 +1203,6 @@ function formatDuration(ms: number): string {
}
async function main() {
- // CommunityReporter disabled per project policy
- // (previously: init + global hooks for uncaughtException/unhandledRejection)
const rewardsBot = new MicrosoftRewardsBot(false)
const crashState = { restarts: 0 }
@@ -1307,7 +1256,6 @@ async function main() {
if (require.main === module) {
main().catch(error => {
log('main', 'MAIN-ERROR', `Error running bots: ${error}`, 'error')
- // CommunityReporter disabled
process.exit(1)
})
}
\ No newline at end of file
diff --git a/src/util/ConclusionWebhook.ts b/src/util/ConclusionWebhook.ts
index fe7f0ec..2265ef5 100644
--- a/src/util/ConclusionWebhook.ts
+++ b/src/util/ConclusionWebhook.ts
@@ -2,23 +2,20 @@ import axios from 'axios'
import { Config } from '../interface/Config'
import { Ntfy } from './Ntfy'
-// Light obfuscation of the avatar URL (base64). Prevents casual editing in config.
-const AVATAR_B64 = 'aHR0cHM6Ly9tZWRpYS5kaXNjb3JkYXBwLm5ldC9hdHRhY2htZW50cy8xNDIxMTYzOTUyOTcyMzY5OTMxLzE0MjExNjQxNDU5OTQyNDAxMTAvbXNuLnBuZz93aWR0aD01MTImZWlnaHQ9NTEy'
-function getAvatarUrl(): string {
- try { return Buffer.from(AVATAR_B64, 'base64').toString('utf-8') } catch { return '' }
-}
+// Avatar URL for webhook (new clean logo)
+const AVATAR_URL = 'https://media.discordapp.net/attachments/1421163952972369931/1421929950377939125/Gc.png'
type WebhookContext = 'summary' | 'ban' | 'security' | 'compromised' | 'spend' | 'error' | 'default'
function pickUsername(ctx: WebhookContext, fallbackColor?: number): string {
switch (ctx) {
- case 'summary': return '📊 MS Rewards Summary'
- case 'ban': return '🚫 Ban Alert'
- case 'security': return '🔐 Security Alert'
- case 'compromised': return '⚠️ Security Issue'
- case 'spend': return '💳 Spend Notice'
- case 'error': return '❌ Error Report'
- default: return fallbackColor === 0xFF0000 ? '❌ Error Report' : '🎯 MS Rewards'
+ case 'summary': return 'MS Rewards - Daily Summary'
+ case 'ban': return 'MS Rewards - Ban Detected'
+ case 'security': return 'MS Rewards - Security Alert'
+ case 'compromised': return 'MS Rewards - Account Compromised'
+ case 'spend': return 'MS Rewards - Purchase Notification'
+ case 'error': return 'MS Rewards - Error Report'
+ default: return fallbackColor === 0xFF0000 ? 'MS Rewards - Error Report' : 'MS Rewards Bot'
}
}
@@ -55,7 +52,7 @@ export async function ConclusionWebhook(config: Config, content: string, payload
const firstColor = payload?.embeds && payload.embeds[0]?.color
const ctx: WebhookContext = payload?.context || (firstColor === 0xFF0000 ? 'error' : 'default')
body.username = pickUsername(ctx, firstColor)
- body.avatar_url = getAvatarUrl()
+ body.avatar_url = AVATAR_URL
// Post to conclusion webhook if configured
const postWithRetry = async (url: string, label: string) => {
diff --git a/src/util/Load.ts b/src/util/Load.ts
index 8ec50b4..c255448 100644
--- a/src/util/Load.ts
+++ b/src/util/Load.ts
@@ -69,8 +69,9 @@ function stripJsonComments(input: string): string {
// Normalize both legacy (flat) and new (nested) config schemas into the flat Config interface
function normalizeConfig(raw: unknown): Config {
+ // Using any here is necessary to support both legacy flat config and new nested config structures
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- const n: any = (raw as any) || {}
+ const n = (raw || {}) as any
// Browser / execution
const headless = n.browser?.headless ?? n.headless ?? false
diff --git a/src/util/Logger.ts b/src/util/Logger.ts
index efb6b7b..ce8b76c 100644
--- a/src/util/Logger.ts
+++ b/src/util/Logger.ts
@@ -3,6 +3,11 @@ import chalk from 'chalk'
import { Ntfy } from './Ntfy'
import { loadConfig } from './Load'
+import { DISCORD } from '../constants'
+
+// Avatar URL for webhook (consistent with ConclusionWebhook)
+const WEBHOOK_AVATAR_URL = 'https://media.discordapp.net/attachments/1421163952972369931/1421929950377939125/Gc.png'
+const WEBHOOK_USERNAME = 'MS Rewards - Live Logs'
type WebhookBuffer = {
lines: string[]
@@ -30,20 +35,31 @@ async function sendBatch(url: string, buf: WebhookBuffer) {
while (buf.lines.length > 0) {
const next = buf.lines[0]!
const projected = currentLength + next.length + (chunk.length > 0 ? 1 : 0)
- if (projected > 1900 && chunk.length > 0) break
+ if (projected > DISCORD.MAX_EMBED_LENGTH && chunk.length > 0) break
buf.lines.shift()
chunk.push(next)
currentLength = projected
}
- const content = chunk.join('\n').slice(0, 1900)
+ const content = chunk.join('\n').slice(0, DISCORD.MAX_EMBED_LENGTH)
if (!content) {
continue
}
+ // Enhanced webhook payload with embed, username and avatar
+ const payload = {
+ username: WEBHOOK_USERNAME,
+ avatar_url: WEBHOOK_AVATAR_URL,
+ embeds: [{
+ description: `\`\`\`\n${content}\n\`\`\``,
+ color: determineColorFromContent(content),
+ timestamp: new Date().toISOString()
+ }]
+ }
+
try {
- await axios.post(url, { content }, { headers: { 'Content-Type': 'application/json' }, timeout: 10000 })
- await new Promise(resolve => setTimeout(resolve, 500))
+ await axios.post(url, payload, { headers: { 'Content-Type': 'application/json' }, timeout: DISCORD.WEBHOOK_TIMEOUT })
+ await new Promise(resolve => setTimeout(resolve, DISCORD.RATE_LIMIT_DELAY))
} catch (error) {
// Re-queue failed batch at front and exit loop
buf.lines = chunk.concat(buf.lines)
@@ -54,6 +70,32 @@ async function sendBatch(url: string, buf: WebhookBuffer) {
buf.sending = false
}
+function determineColorFromContent(content: string): number {
+ const lower = content.toLowerCase()
+ // Security/Ban alerts - Red
+ if (lower.includes('[banned]') || lower.includes('[security]') || lower.includes('suspended') || lower.includes('compromised')) {
+ return DISCORD.COLOR_RED
+ }
+ // Errors - Dark Red
+ if (lower.includes('[error]') || lower.includes('✗')) {
+ return DISCORD.COLOR_CRIMSON
+ }
+ // Warnings - Orange/Yellow
+ if (lower.includes('[warn]') || lower.includes('⚠')) {
+ return DISCORD.COLOR_ORANGE
+ }
+ // Success - Green
+ if (lower.includes('[ok]') || lower.includes('✓') || lower.includes('complet')) {
+ return DISCORD.COLOR_GREEN
+ }
+ // Info/Main - Blue
+ if (lower.includes('[main]')) {
+ return DISCORD.COLOR_BLUE
+ }
+ // Default - Gray
+ return 0x95A5A6 // Gray
+}
+
function enqueueWebhookLog(url: string, line: string) {
const buf = getBuffer(url)
buf.lines.push(line)
@@ -61,7 +103,7 @@ function enqueueWebhookLog(url: string, line: string) {
buf.timer = setTimeout(() => {
buf.timer = undefined
void sendBatch(url, buf)
- }, 750)
+ }, DISCORD.DEBOUNCE_DELAY)
}
}
diff --git a/src/util/Ntfy.ts b/src/util/Ntfy.ts
index b3d447a..b8e2ae4 100644
--- a/src/util/Ntfy.ts
+++ b/src/util/Ntfy.ts
@@ -20,14 +20,8 @@ export async function Ntfy(message: string, type: keyof typeof NOTIFICATION_TYPE
...(config.authToken && { Authorization: `Bearer ${config.authToken}` })
}
- const response = await axios.post(`${config.url}/${config.topic}`, message, { headers })
-
- if (response.status === 200) {
- console.log('NTFY notification successfully sent.')
- } else {
- console.error(`NTFY notification failed with status ${response.status}`)
- }
+ await axios.post(`${config.url}/${config.topic}`, message, { headers })
} catch (error) {
- console.error('Failed to send NTFY notification:', error)
+ // Silently fail - NTFY is a non-critical notification service
}
}
\ No newline at end of file
diff --git a/src/util/QueryDiversityEngine.ts b/src/util/QueryDiversityEngine.ts
index a9a3ef5..b640e7f 100644
--- a/src/util/QueryDiversityEngine.ts
+++ b/src/util/QueryDiversityEngine.ts
@@ -43,7 +43,7 @@ export class QueryDiversityEngine {
const queries = await this.getFromSource(sourceName)
allQueries.push(...queries.slice(0, this.config.maxQueriesPerSource))
} catch (error) {
- console.warn(`Failed to fetch from ${sourceName}:`, error instanceof Error ? error.message : error)
+ // Silently fail and try other sources
}
}
@@ -89,7 +89,8 @@ export class QueryDiversityEngine {
queries = this.getLocalFallback(20)
break
default:
- console.warn(`Unknown source: ${source}`)
+ // Unknown source, skip silently
+ break
}
this.cache.set(source, {