mirror of
https://github.com/TheNetsky/Microsoft-Rewards-Script.git
synced 2026-01-10 18:36:17 +00:00
V2.3.0 Optimization (#380)
* 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
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@ dist/
|
||||
node_modules/
|
||||
.vscode/
|
||||
.github/
|
||||
diagnostic/
|
||||
accounts.json
|
||||
notes
|
||||
accounts.dev.json
|
||||
|
||||
12
README.md
12
README.md
@@ -17,7 +17,7 @@
|
||||
|
||||
<br>
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

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

|
||||
|
||||
<br><br>
|
||||
<br>
|
||||
|
||||
**Made with ❤️ by the open source community**
|
||||
|
||||
<br>
|
||||
|
||||

|
||||
|
||||
<br><br>
|
||||
|
||||
<img src="https://capsule-render.vercel.app/api?type=waving&height=120&color=gradient&customColorList=0,2,2,5,6,8§ion=footer" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
209
docs/accounts.md
209
docs/accounts.md
@@ -1,94 +1,159 @@
|
||||
# 👤 Accounts & TOTP (2FA)
|
||||
# 👤 Accounts & 2FA Setup
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🔐 Secure Microsoft account setup with 2FA support**
|
||||
*Everything you need to configure authentication*
|
||||
|
||||
</div>
|
||||
**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
|
||||
### 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)**
|
||||
|
||||
210
docs/buy-mode.md
210
docs/buy-mode.md
@@ -1,64 +1,53 @@
|
||||
# 💳 Buy Mode
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🛒 Manual redemption with live point monitoring**
|
||||
*Track your spending while maintaining full control*
|
||||
|
||||
</div>
|
||||
**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)**
|
||||
|
||||
@@ -1,145 +1,49 @@
|
||||
# 📊 Discord Conclusion Webhook
|
||||
# 📊 Discord Webhooks
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🎯 Comprehensive session summaries via Discord**
|
||||
*Complete execution reports delivered instantly*
|
||||
|
||||
</div>
|
||||
**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
|
||||
**[← Back to Hub](./index.md)** | **[Config Guide](./config.md)**
|
||||
|
||||
800
docs/config.md
800
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: `<sessionPath>/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. |
|
||||
|
||||
<details>
|
||||
|
||||
<summary><strong>Click to expand all options</strong></summary>---
|
||||
|
||||
## 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: `<sessionPath>/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
|
||||
|
||||
</details>
|
||||
|
||||
### 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)**
|
||||
|
||||
@@ -1,32 +1,24 @@
|
||||
# 🔍 Diagnostics & Error Capture
|
||||
# 🔍 Diagnostics
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🛠️ Automatic error screenshots and HTML snapshots**
|
||||
*Debug smarter with visual evidence*
|
||||
|
||||
</div>
|
||||
**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/<timestamp>-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
|
||||
**[← Back to Hub](./index.md)** | **[Config Guide](./config.md)**
|
||||
|
||||
191
docs/docker.md
191
docs/docker.md
@@ -1,90 +1,167 @@
|
||||
# 🐳 Docker Guide
|
||||
|
||||
<div align="center">
|
||||
|
||||
**⚡ Lightweight containerized deployment**
|
||||
*Automated Microsoft Rewards with minimal Docker footprint*
|
||||
|
||||
</div>
|
||||
**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
|
||||
**[← Back to Hub](./index.md)** | **[Getting Started](./getting-started.md)**
|
||||
|
||||
@@ -1,136 +1,148 @@
|
||||
# 🚀 Getting Started
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🎯 From zero to earning Microsoft Rewards points in minutes**
|
||||
*Complete setup guide for beginners*
|
||||
|
||||
</div>
|
||||
**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)
|
||||
|
||||
<div align="center">
|
||||
|
||||
### **🎬 One Command, Total Automation**
|
||||
|
||||
</div>
|
||||
|
||||
```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)
|
||||
|
||||
<details>
|
||||
<summary><strong>📖 Prefer step-by-step? Click here</strong></summary>
|
||||
<summary><strong>Click to expand</strong></summary>
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## 🎯 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
|
||||
**[← Back to Hub](./index.md)** | **[All Docs](./index.md)**
|
||||
|
||||
@@ -1,32 +1,24 @@
|
||||
# 🤖 Humanization (Human Mode)
|
||||
# 🤖 Humanization
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🎭 Natural automation that mimics human behavior**
|
||||
*Subtle gestures for safer operation*
|
||||
|
||||
</div>
|
||||
**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)**
|
||||
|
||||
107
docs/index.md
107
docs/index.md
@@ -1,62 +1,79 @@
|
||||
# 📚 Microsoft Rewards Script V2 Docs
|
||||
# 📚 Documentation Hub
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🎯 Your companion for mastering the automation stack**
|
||||
*Curated guides, verified against the current codebase*
|
||||
|
||||
</div>
|
||||
**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 |
|
||||
| **[<EFBFBD> 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)**
|
||||
|
||||
355
docs/jobstate.md
355
docs/jobstate.md
@@ -1,339 +1,112 @@
|
||||
# 💾 Job State Persistence
|
||||
# 💾 Job State
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🔄 Resume interrupted tasks and track progress across runs**
|
||||
*Never lose your progress again*
|
||||
|
||||
</div>
|
||||
**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
|
||||
**[← Back to Hub](./index.md)** | **[Config Guide](./config.md)**
|
||||
|
||||
421
docs/ntfy.md
421
docs/ntfy.md
@@ -1,86 +1,79 @@
|
||||
# 📱 NTFY Push Notifications
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🔔 Real-time push notifications to your devices**
|
||||
*Stay informed wherever you are*
|
||||
|
||||
</div>
|
||||
**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
|
||||
**[← Back to Hub](./index.md)** | **[Config Guide](./config.md)**
|
||||
|
||||
623
docs/proxy.md
623
docs/proxy.md
@@ -1,611 +1,126 @@
|
||||
# 🌐 Proxy Configuration
|
||||
# 🌐 Proxy Setup
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🔒 Route traffic through proxy servers for privacy and flexibility**
|
||||
*Enhanced anonymity and geographic control*
|
||||
|
||||
</div>
|
||||
**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.*.*.*",
|
||||
"<local>"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎛️ 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
|
||||
**[← Back to Hub](./index.md)** | **[Config Guide](./config.md)**
|
||||
|
||||
692
docs/schedule.md
692
docs/schedule.md
@@ -1,232 +1,37 @@
|
||||
# ⏰ Scheduler & Automation
|
||||
# ⏰ Scheduler
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🚀 Built-in scheduler for automated daily execution**
|
||||
*Set it and forget it*
|
||||
|
||||
</div>
|
||||
**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<input.length;i++){const ch=input[i],next=input[i+1];if(inLine){if(ch==='\n'||ch==='\r'){inLine=false;out+=ch;}continue;}if(inBlock){if(ch==='*'&&next==='/' ){inBlock=false;i++;}continue;}if(inString){out+=ch;if(ch==='\\'){i++;if(i<input.length)out+=input[i];continue;}if(ch===stringChar)inString=false;continue;}if(ch==='"'||ch==='\''){inString=true;stringChar=ch;out+=ch;continue;}if(ch==='/'&&next==='/' ){inLine=true;i++;continue;}if(ch==='/'&&next==='*' ){inBlock=true;i++;continue;}out+=ch;}return out;};console.log(JSON.parse(strip(fs.readFileSync('src/config.jsonc','utf8'))));"
|
||||
|
||||
# Check running processes
|
||||
Get-Process | Where-Object {$_.ProcessName -eq "node"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Performance & Best Practices
|
||||
|
||||
### **Optimal Timing**
|
||||
- **🌅 Morning (7-10 AM)** — Catch daily resets
|
||||
- **🌆 Evening (7-10 PM)** — Complete remaining tasks
|
||||
- **❌ Avoid peak hours** — Reduce detection during high traffic
|
||||
|
||||
### **Pass Recommendations**
|
||||
- **1 pass** — Safest, good for most users
|
||||
- **2-3 passes** — Balance of points vs. risk
|
||||
- **4+ passes** — Higher risk, development only
|
||||
|
||||
### **Monitoring**
|
||||
- ✅ Check logs regularly for errors
|
||||
- ✅ Monitor point collection trends
|
||||
- ✅ Verify scheduler status weekly
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Alternative Solutions
|
||||
|
||||
### **Windows Task Scheduler**
|
||||
```powershell
|
||||
# Create scheduled task
|
||||
schtasks /create /tn "MS-Rewards" /tr "npm start" /sc daily /st 09:00 /sd 01/01/2025
|
||||
```
|
||||
|
||||
### **PowerShell Scheduled Job**
|
||||
```powershell
|
||||
# Register scheduled job
|
||||
Register-ScheduledJob -Name "MSRewards" -ScriptBlock {cd "C:\path\to\project"; npm start} -Trigger (New-JobTrigger -Daily -At 9am)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Guides
|
||||
|
||||
- **[Getting Started](./getting-started.md)** — Initial setup and configuration
|
||||
- **[Humanization](./humanization.md)** — Natural behavior patterns
|
||||
- **[Docker](./docker.md)** — Container deployment
|
||||
- **[Job State](./jobstate.md)** — Execution state management
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Daily Run
|
||||
```json
|
||||
{
|
||||
"schedule": {
|
||||
"enabled": true,
|
||||
"time": "08:00",
|
||||
"timeZone": "America/New_York"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Multiple Daily Passes
|
||||
```json
|
||||
{
|
||||
"schedule": {
|
||||
"enabled": true,
|
||||
"time": "10:00",
|
||||
"timeZone": "Europe/London",
|
||||
"runImmediatelyOnStart": false
|
||||
},
|
||||
"passesPerRun": 3
|
||||
}
|
||||
```
|
||||
|
||||
### Development Testing
|
||||
```json
|
||||
{
|
||||
"schedule": {
|
||||
"enabled": true,
|
||||
"time": "00:01",
|
||||
"timeZone": "UTC",
|
||||
"runImmediatelyOnStart": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Supported Timezones
|
||||
|
||||
Common IANA timezone identifiers:
|
||||
|
||||
### 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)
|
||||
|
||||
### UTC Variants
|
||||
- `UTC` (Coordinated Universal Time)
|
||||
- `GMT` (Greenwich Mean Time)
|
||||
|
||||
## Running the Scheduler
|
||||
|
||||
### Development Mode
|
||||
```bash
|
||||
npm run ts-schedule
|
||||
```
|
||||
|
||||
### Production Mode
|
||||
```bash
|
||||
npm run build
|
||||
npm run start:schedule
|
||||
```
|
||||
|
||||
### Optional Randomization and Watchdog
|
||||
|
||||
You can introduce slight randomness to the start times and protect against stuck runs:
|
||||
|
||||
- `SCHEDULER_INITIAL_JITTER_MINUTES_MIN` / `SCHEDULER_INITIAL_JITTER_MINUTES_MAX`
|
||||
- Adds a one‑time random delay before the very first run after the scheduler starts.
|
||||
- Example: `SCHEDULER_INITIAL_JITTER_MINUTES_MIN=5` and `SCHEDULER_INITIAL_JITTER_MINUTES_MAX=20` delays the first run by 5–20 minutes.
|
||||
|
||||
- `SCHEDULER_DAILY_JITTER_MINUTES_MIN` / `SCHEDULER_DAILY_JITTER_MINUTES_MAX`
|
||||
- Adds an extra random delay to each daily scheduled execution.
|
||||
- Example: 2–10 minutes of daily jitter to avoid exact same second each day.
|
||||
|
||||
- `SCHEDULER_PASS_TIMEOUT_MINUTES`
|
||||
- Kills a stuck pass after N minutes (default 180). Useful if the underlying browser gets stuck.
|
||||
|
||||
- `SCHEDULER_FORK_PER_PASS`
|
||||
- Defaults to `true`. When `true`, each pass runs in a child Node process so a stuck pass can be terminated without killing the scheduler. Set to `false` to run passes in‑process (not recommended).
|
||||
|
||||
### Background Execution
|
||||
```bash
|
||||
# Linux/macOS (background process)
|
||||
nohup npm run start:schedule > 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<input.length;i++){const ch=input[i],next=input[i+1];if(inLine){if(ch==='\n'||ch==='\r'){inLine=false;out+=ch;}continue;}if(inBlock){if(ch==='*'&&next==='/' ){inBlock=false;i++;}continue;}if(inString){out+=ch;if(ch==='\\'){i++;if(i<input.length)out+=input[i];continue;}if(ch===stringChar)inString=false;continue;}if(ch==='"'||ch==='\''){inString=true;stringChar=ch;out+=ch;continue;}if(ch==='/'&&next==='/' ){inLine=true;i++;continue;}if(ch==='/'&&next==='*' ){inBlock=true;i++;continue;}out+=ch;}return out;};console.log(JSON.parse(strip(fs.readFileSync('src/config.jsonc','utf8'))));"
|
||||
|
||||
# Check process status
|
||||
ps aux | grep "start:schedule"
|
||||
```
|
||||
|
||||
## Alternative Solutions
|
||||
|
||||
### External Cron (Linux/macOS)
|
||||
```bash
|
||||
# Crontab entry for 9 AM daily
|
||||
0 9 * * * cd /path/to/MSN-V2 && npm start
|
||||
|
||||
# Multiple times per day
|
||||
0 9,15,21 * * * cd /path/to/MSN-V2 && npm start
|
||||
**Validate config:**
|
||||
```powershell
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
### Windows Task Scheduler
|
||||
- Create scheduled task via Task Scheduler
|
||||
- Set trigger for daily execution
|
||||
- Configure action to run `npm start` in project directory
|
||||
---
|
||||
|
||||
### Docker Cron
|
||||
```dockerfile
|
||||
# Add to Dockerfile
|
||||
RUN apt-get update && apt-get install -y cron
|
||||
COPY crontab /etc/cron.d/rewards-cron
|
||||
RUN crontab /etc/cron.d/rewards-cron
|
||||
```
|
||||
|
||||
### Docker + Built-in Scheduler
|
||||
Au lieu d'utiliser cron, vous pouvez lancer le scheduler intégré dans le conteneur (un seul process long‑vivant) :
|
||||
## 🐳 Docker Integration
|
||||
|
||||
### Built-in Scheduler (Recommended)
|
||||
```yaml
|
||||
services:
|
||||
microsoft-rewards-script:
|
||||
rewards:
|
||||
build: .
|
||||
command: ["npm", "run", "start:schedule"]
|
||||
environment:
|
||||
TZ: Europe/Paris
|
||||
command: ["npm", "run", "start:schedule"]
|
||||
```
|
||||
|
||||
Dans ce mode :
|
||||
- `passesPerRun` fonctionne (exécutera plusieurs passes à chaque horaire interne défini par `src/config.jsonc`).
|
||||
- Vous n'avez plus besoin de `CRON_SCHEDULE` ni de `run_daily.sh`.
|
||||
Uses config from `src/config.jsonc`.
|
||||
|
||||
### Docker + External Cron (par défaut du projet)
|
||||
Si vous préférez la planification par cron système dans le conteneur (valeur par défaut du projet) :
|
||||
- Utilisez `CRON_SCHEDULE` (ex.: `0 7,16,20 * * *`).
|
||||
- `run_daily.sh` introduit un délai aléatoire (par défaut 5–50 min) et un lockfile pour éviter les chevauchements.
|
||||
- `RUN_ON_START=true` déclenche une exécution immédiate au démarrage du conteneur (sans délai aléatoire).
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
## 📚 Next Steps
|
||||
|
||||
### System Resources
|
||||
- Minimal CPU usage between runs
|
||||
- Low memory footprint when idle
|
||||
- No network activity during waiting periods
|
||||
**Want natural behavior?**
|
||||
→ **[Humanization Guide](./humanization.md)**
|
||||
|
||||
### Startup Time
|
||||
- Fast initialization (< 1 second)
|
||||
- Quick timezone calculations
|
||||
- Immediate scheduling of next run
|
||||
**Need notifications?**
|
||||
→ **[Discord Webhooks](./conclusionwebhook.md)**
|
||||
|
||||
### Reliability
|
||||
- Robust error handling
|
||||
- Automatic recovery from failures
|
||||
- Consistent execution timing
|
||||
**Docker setup?**
|
||||
→ **[Docker Guide](./docker.md)**
|
||||
|
||||
---
|
||||
|
||||
**[← Back to Hub](./index.md)** | **[Getting Started](./getting-started.md)**
|
||||
|
||||
323
docs/security.md
323
docs/security.md
@@ -1,296 +1,201 @@
|
||||
# 🔒 Security & Privacy Guide
|
||||
# 🔒 Security Guide
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🛡️ Comprehensive security measures and incident response**
|
||||
*Protect your accounts and maintain privacy*
|
||||
|
||||
</div>
|
||||
**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)**
|
||||
|
||||
431
docs/update.md
431
docs/update.md
@@ -1,371 +1,48 @@
|
||||
# 🔄 Auto-Update System
|
||||
# 🔄 Auto-Update
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🚀 Automatic updates to keep your installation current**
|
||||
*Set it and forget it*
|
||||
|
||||
</div>
|
||||
**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
|
||||
| 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)**
|
||||
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<QuizData> {
|
||||
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<boolean> {
|
||||
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<boolean> {
|
||||
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()
|
||||
|
||||
@@ -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<number> {
|
||||
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<Page> {
|
||||
try {
|
||||
await this.bot.utils.wait(1000)
|
||||
|
||||
304
src/config.jsonc
304
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"
|
||||
}
|
||||
}
|
||||
|
||||
67
src/constants.ts
Normal file
67
src/constants.ts
Normal file
@@ -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
|
||||
@@ -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<string>(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<string,string> = {
|
||||
'recovery-email-mismatch':'#recovery-email-mismatch',
|
||||
'we-cant-sign-you-in':'#we-cant-sign-you-in-blocked'
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
192
src/index.ts
192
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)
|
||||
})
|
||||
}
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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, {
|
||||
|
||||
Reference in New Issue
Block a user