feat: Enhance update mechanism with anti-loop protection and improved logging

- Implemented a restart counter to prevent infinite update loops.
- Added checks for update success using marker files.
- Improved logging for update attempts and failures.
- Created comprehensive documentation for npm commands and setup processes.
- Introduced a new update system using GitHub API for seamless updates.
- Added troubleshooting guidelines for common issues.
This commit is contained in:
2025-11-09 20:13:30 +01:00
parent 842218ca4d
commit e03761adfc
10 changed files with 1276 additions and 745 deletions

4
.gitignore vendored
View File

@@ -17,3 +17,7 @@ accounts.main.jsonc
.playwright-chromium-installed .playwright-chromium-installed
*.log *.log
.update-backup/ .update-backup/
.update-download.zip
.update-extract/
.update-happened
.update-restart-count

362
docs/commands.md Normal file
View File

@@ -0,0 +1,362 @@
# NPM Commands Reference
This guide explains all available npm commands and when to use them.
## 🚀 Essential Commands
### `npm start`
**Start the bot** - Use this to run the Microsoft Rewards Bot after setup.
```bash
npm start
```
**What it does:**
- Runs the compiled JavaScript from `dist/index.js`
- Checks for automatic updates (if enabled)
- Executes reward earning tasks
- Fastest way to run the bot
**When to use:**
- Daily bot execution
- After setup is complete
- When you want to earn points
---
### `npm run setup`
**First-time installation** - Run this only once when setting up the project.
```bash
npm run setup
```
**What it does:**
1. Creates `accounts.jsonc` from template
2. Guides you through account configuration
3. Installs dependencies (`npm install`)
4. Builds TypeScript (`npm run build`)
5. Installs Playwright browsers
**When to use:**
- First time installing the bot
- After fresh git clone
- To reconfigure accounts
**Important:** This does NOT start the bot automatically. After setup, run `npm start`.
---
## 🔨 Development Commands
### `npm run build`
**Build TypeScript to JavaScript** - Compiles the project.
```bash
npm run build
```
**What it does:**
- Compiles `src/*.ts` files to `dist/*.js`
- Generates source maps for debugging
- Required before running `npm start`
**When to use:**
- After modifying TypeScript source code
- Before starting the bot with `npm start`
- After pulling updates from git
---
### `npm run dev`
**Development mode** - Run TypeScript directly without building.
```bash
npm run dev
```
**What it does:**
- Runs TypeScript files directly with `ts-node`
- No build step required
- Slower but convenient for development
- Includes `-dev` flag for debug features
**When to use:**
- During development/testing
- When making code changes
- Quick testing without full build
---
### `npm run ts-start`
**TypeScript direct execution** - Like `dev` but without debug flags.
```bash
npm run ts-start
```
**When to use:**
- Alternative to `npm run dev`
- Running TypeScript without full build
---
## 🧹 Maintenance Commands
### `npm run clean`
**Remove build artifacts** - Deletes the `dist` folder.
```bash
npm run clean
```
**When to use:**
- Before fresh rebuild
- To clear stale compiled code
- Troubleshooting build issues
---
### `npm run install-deps`
**Install all dependencies** - Fresh installation of dependencies and browsers.
```bash
npm run install-deps
```
**What it does:**
- Runs `npm install` to install Node.js packages
- Installs Playwright Chromium browser
**When to use:**
- After deleting `node_modules`
- Setting up on new machine
- Troubleshooting dependency issues
---
### `npm run typecheck`
**Check TypeScript types** - Validates code without building.
```bash
npm run typecheck
```
**When to use:**
- Checking for type errors
- Before committing code
- Part of CI/CD pipeline
---
## 🧪 Testing & Quality
### `npm test`
**Run unit tests** - Execute test suite.
```bash
npm test
```
**When to use:**
- Verifying code changes
- Before submitting pull requests
- Continuous integration
---
### `npm run lint`
**Check code style** - ESLint validation.
```bash
npm run lint
```
**When to use:**
- Checking code formatting
- Before commits
- Maintaining code quality
---
## 📊 Dashboard Commands
### `npm run dashboard`
**Start web dashboard only** - Web interface without bot execution.
```bash
npm run dashboard
```
**What it does:**
- Launches web interface on http://localhost:3000
- Provides monitoring and control panel
- Does NOT start reward earning
**When to use:**
- Monitoring bot status
- Viewing logs remotely
- Configuring settings via UI
---
### `npm run dashboard-dev`
**Dashboard development mode** - TypeScript version of dashboard.
```bash
npm run dashboard-dev
```
**When to use:**
- Dashboard development/testing
- Quick dashboard testing without build
---
## 🤖 Account Creation
### `npm run creator`
**Account creation wizard** - Create new Microsoft accounts.
```bash
npm run creator
```
**When to use:**
- Creating new Microsoft accounts
- Bulk account creation
- Testing account setup
---
## 🐳 Docker Commands
### `npm run create-docker`
**Build Docker image** - Create containerized version.
```bash
npm run create-docker
```
**When to use:**
- Deploying with Docker
- Creating container image
- Testing Docker setup
---
## 🆘 Troubleshooting Commands
### `npm run kill-chrome-win` (Windows only)
**Force close Chrome browsers** - Kill stuck browser processes.
```bash
npm run kill-chrome-win
```
**When to use:**
- Browser processes stuck
- Windows only
- Before restarting bot
---
## 📝 Command Comparison
| Command | Speed | Purpose | When to Use |
|---------|-------|---------|-------------|
| `npm start` | ⚡ Fast | Run bot | Daily use |
| `npm run dev` | 🐌 Slow | Development | Code changes |
| `npm run build` | ⏱️ Medium | Compile TS | Before start |
| `npm run setup` | ⏱️ Medium | First install | Once only |
---
## Common Workflows
### First-Time Setup
```bash
# 1. Run setup wizard
npm run setup
# 2. Start the bot
npm start
```
### Daily Usage
```bash
npm start
```
### After Code Changes
```bash
# Method 1: Build then run (faster)
npm run build
npm start
# Method 2: Direct run (slower)
npm run dev
```
### After Pulling Updates
```bash
# If dependencies changed
npm install
# Rebuild
npm run build
# Start bot
npm start
```
### Troubleshooting
```bash
# Clean install
npm run clean
rm -rf node_modules package-lock.json
npm run install-deps
# Rebuild
npm run build
# Test
npm start
```
---
## ❓ FAQ
### Why does `npm run start` trigger updates?
The bot automatically checks for updates on startup (configurable in `config.jsonc`). To disable:
```jsonc
{
"update": {
"enabled": false
}
}
```
### What's the difference between `npm start` and `npm run start`?
**No functional difference** - both run the same command. `npm start` is a shorthand for `npm run start`.
### Should I use `npm start` or `npm run dev`?
- **Production/Daily use:** `npm start` (faster)
- **Development:** `npm run dev` (no build needed)
### How do I completely reset the project?
```bash
npm run clean
rm -rf node_modules package-lock.json dist
npm run setup
```
---
## Need Help?
- **Getting Started:** [docs/getting-started.md](getting-started.md)
- **Configuration:** [docs/config.md](config.md)
- **Troubleshooting:** [docs/troubleshooting.md](troubleshooting.md)
- **Discord:** https://discord.gg/k5uHkx9mne

View File

@@ -17,9 +17,10 @@
"homepage": "https://github.com/Obsidian-wtf/Microsoft-Rewards-Bot#readme", "homepage": "https://github.com/Obsidian-wtf/Microsoft-Rewards-Bot#readme",
"scripts": { "scripts": {
"clean": "rimraf dist", "clean": "rimraf dist",
"pre-build": "npm i && npm run clean && node -e \"process.exit(process.env.SKIP_PLAYWRIGHT_INSTALL?0:1)\" || npx playwright install chromium", "install-deps": "npm install && npx playwright install chromium",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"build": "tsc", "build": "tsc",
"postbuild": "node -e \"console.log('\\n✅ Build complete! Run \\\"npm start\\\" to launch the bot.\\n')\"",
"test": "node --test --loader ts-node/esm tests/**/*.test.ts", "test": "node --test --loader ts-node/esm tests/**/*.test.ts",
"start": "node --enable-source-maps ./dist/index.js", "start": "node --enable-source-maps ./dist/index.js",
"ts-start": "node --loader ts-node/esm ./src/index.ts", "ts-start": "node --loader ts-node/esm ./src/index.ts",
@@ -28,7 +29,6 @@
"dashboard": "node --enable-source-maps ./dist/index.js -dashboard", "dashboard": "node --enable-source-maps ./dist/index.js -dashboard",
"dashboard-dev": "ts-node ./src/index.ts -dashboard", "dashboard-dev": "ts-node ./src/index.ts -dashboard",
"lint": "eslint \"src/**/*.{ts,tsx}\"", "lint": "eslint \"src/**/*.{ts,tsx}\"",
"prepare": "npm run build",
"setup": "node ./setup/update/setup.mjs", "setup": "node ./setup/update/setup.mjs",
"kill-chrome-win": "powershell -Command \"Get-Process | Where-Object { $_.MainModule.FileVersionInfo.FileDescription -eq 'Google Chrome for Testing' } | ForEach-Object { Stop-Process -Id $_.Id -Force }\"", "kill-chrome-win": "powershell -Command \"Get-Process | Where-Object { $_.MainModule.FileVersionInfo.FileDescription -eq 'Google Chrome for Testing' } | ForEach-Object { Stop-Process -Id $_.Id -Force }\"",
"create-docker": "docker build -t microsoft-rewards-bot ." "create-docker": "docker build -t microsoft-rewards-bot ."

123
setup/README.md Normal file
View File

@@ -0,0 +1,123 @@
# Setup Scripts
This folder contains setup and update scripts for the Microsoft Rewards Bot.
## Files
### setup.bat / setup.sh
**First-time installation scripts** for Windows (.bat) and Linux/macOS (.sh).
**What they do:**
1. Check prerequisites (Node.js, npm)
2. Create `accounts.jsonc` from template
3. Guide you through account configuration
4. Install dependencies (`npm install`)
5. Build TypeScript project (`npm run build`)
6. Install Playwright Chromium browser
**Usage:**
```bash
# Windows
.\setup\setup.bat
# Linux/macOS
./setup/setup.sh
```
**Important:** These scripts do NOT start the bot automatically. After setup, run:
```bash
npm start
```
### update/update.mjs
**Automatic update script** that keeps your bot up-to-date with the latest version.
**Features:**
- Two update methods: Git-based or GitHub API (no Git needed)
- Preserves your configuration and accounts
- No merge conflicts with GitHub API method
- Automatic dependency installation and rebuild
**Usage:**
```bash
# Auto-detect method from config.jsonc
node setup/update/update.mjs
# Force GitHub API method (recommended)
node setup/update/update.mjs --no-git
# Force Git method
node setup/update/update.mjs --git
```
**Automatic updates:** The bot checks for updates on startup (controlled by `update.enabled` in config.jsonc).
### update/setup.mjs
**Interactive setup wizard** used by setup.bat/setup.sh.
This is typically not run directly - use the wrapper scripts instead.
## Quick Start Guide
### First-time setup:
**Windows:**
```batch
.\setup\setup.bat
```
**Linux/macOS:**
```bash
chmod +x setup/setup.sh
./setup/setup.sh
```
### Daily usage:
```bash
# Start the bot
npm start
# Start with TypeScript (development)
npm run dev
# View dashboard
npm run dashboard
```
### Configuration:
- **Accounts:** Edit `src/accounts.jsonc`
- **Settings:** Edit `src/config.jsonc`
- **Documentation:** See `docs/` folder
## Troubleshooting
### "npm not found"
Install Node.js from https://nodejs.org/ (v20 or newer recommended)
### "Setup failed"
1. Delete `node_modules` folder
2. Delete `package-lock.json` file
3. Run setup again
### "Build failed"
```bash
npm run clean
npm run build
```
### Update issues
If automatic updates fail, manually update:
```bash
git pull origin main
npm install
npm run build
```
## Need Help?
- **Documentation:** `docs/index.md`
- **Getting Started:** `docs/getting-started.md`
- **Troubleshooting:** `docs/troubleshooting.md`
- **Discord:** https://discord.gg/k5uHkx9mne

View File

@@ -1,25 +1,76 @@
@echo off @echo off
setlocal setlocal EnableDelayedExpansion
REM Wrapper to run setup via npm (Windows)
REM Navigates to project root and runs npm run setup REM ========================================
REM Microsoft Rewards Bot - Setup (Windows)
REM ========================================
REM This script performs first-time setup:
REM 1. Check prerequisites (Node.js, npm)
REM 2. Run setup wizard (accounts + config)
REM 3. Install dependencies
REM 4. Build TypeScript project
REM
REM After setup, run the bot with: npm start
REM ========================================
set SCRIPT_DIR=%~dp0 set SCRIPT_DIR=%~dp0
set PROJECT_ROOT=%SCRIPT_DIR%.. set PROJECT_ROOT=%SCRIPT_DIR%..
if not exist "%PROJECT_ROOT%\package.json" ( echo.
echo [ERROR] package.json not found in project root. echo ========================================
echo Microsoft Rewards Bot - Setup
echo ========================================
echo.
REM Check if Node.js/npm are installed
where npm >nul 2>nul
if errorlevel 1 (
echo [ERROR] npm not found!
echo.
echo Please install Node.js from: https://nodejs.org/
echo Recommended version: v20 or newer
echo.
pause pause
exit /b 1 exit /b 1
) )
echo Navigating to project root... for /f "tokens=*" %%i in ('npm -v 2^>nul') do set NPM_VERSION=%%i
echo [OK] npm detected: v!NPM_VERSION!
echo.
REM Check if package.json exists
if not exist "%PROJECT_ROOT%\package.json" (
echo [ERROR] package.json not found in project root.
echo Current directory: %CD%
echo Project root: %PROJECT_ROOT%
echo.
pause
exit /b 1
)
REM Navigate to project root
cd /d "%PROJECT_ROOT%" cd /d "%PROJECT_ROOT%"
echo Running setup script via npm... REM Run setup script
echo Running setup wizard...
echo.
call npm run setup call npm run setup
set EXITCODE=%ERRORLEVEL% set EXITCODE=%ERRORLEVEL%
echo. echo.
echo Setup finished with exit code %EXITCODE%. if %EXITCODE% EQU 0 (
echo Press Enter to close. echo ========================================
pause >NUL echo Setup Complete!
echo ========================================
echo.
echo To start the bot: npm start
echo.
) else (
echo ========================================
echo Setup Failed ^(Exit Code: %EXITCODE%^)
echo ========================================
echo.
)
pause
exit /b %EXITCODE% exit /b %EXITCODE%

View File

@@ -1,35 +1,84 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
# Wrapper to run setup via npm (Linux/macOS) # ========================================
# Microsoft Rewards Bot - Setup (Linux/macOS)
# ========================================
# This script performs first-time setup:
# 1. Check prerequisites (Node.js, npm, Git)
# 2. Run setup wizard (accounts + config)
# 3. Install dependencies
# 4. Build TypeScript project
#
# After setup, run the bot with: npm start
# ========================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
echo "=== Prerequisite Check ===" echo ""
echo "========================================"
echo " Microsoft Rewards Bot - Setup"
echo "========================================"
echo ""
# Check prerequisites
echo "=== Prerequisites Check ==="
echo ""
if command -v npm >/dev/null 2>&1; then if command -v npm >/dev/null 2>&1; then
NPM_VERSION="$(npm -v 2>/dev/null || true)" NPM_VERSION="$(npm -v 2>/dev/null || echo 'unknown')"
echo "npm detected: ${NPM_VERSION}" echo "[OK] npm detected: v${NPM_VERSION}"
else else
echo "[ERROR] npm not detected." echo "[ERROR] npm not found!"
echo " Install Node.js and npm from nodejs.org or your package manager" echo ""
echo "Please install Node.js from: https://nodejs.org/"
echo "Recommended version: v20 or newer"
echo ""
echo "Alternatively, use your package manager:"
echo " • Ubuntu/Debian: sudo apt install nodejs npm"
echo " • macOS: brew install node"
echo " • Fedora: sudo dnf install nodejs npm"
exit 1 exit 1
fi fi
if command -v git >/dev/null 2>&1; then if command -v git >/dev/null 2>&1; then
GIT_VERSION="$(git --version 2>/dev/null || true)" GIT_VERSION="$(git --version 2>/dev/null | cut -d' ' -f3)"
echo "Git detected: ${GIT_VERSION}" echo "[OK] Git detected: v${GIT_VERSION}"
else else
echo "[WARN] Git not detected." echo "[WARN] Git not detected (optional for setup, required for updates)"
echo " Install (Linux): e.g. 'sudo apt install git' (or your distro equivalent)." echo " • Ubuntu/Debian: sudo apt install git"
echo " • macOS: brew install git"
echo " • Fedora: sudo dnf install git"
fi fi
if [ ! -f "${PROJECT_ROOT}/package.json" ]; then if [ ! -f "${PROJECT_ROOT}/package.json" ]; then
echo ""
echo "[ERROR] package.json not found at ${PROJECT_ROOT}" >&2 echo "[ERROR] package.json not found at ${PROJECT_ROOT}" >&2
exit 1 exit 1
fi fi
echo echo ""
echo "=== Running setup script via npm ===" echo "=== Running Setup Wizard ==="
echo ""
cd "${PROJECT_ROOT}" cd "${PROJECT_ROOT}"
exec npm run setup npm run setup
EXITCODE=$?
echo ""
if [ $EXITCODE -eq 0 ]; then
echo "========================================"
echo " Setup Complete!"
echo "========================================"
echo ""
echo "To start the bot: npm start"
echo ""
else
echo "========================================"
echo " Setup Failed (Exit Code: $EXITCODE)"
echo "========================================"
echo ""
fi
exit $EXITCODE

175
setup/update/README.md Normal file
View File

@@ -0,0 +1,175 @@
# Update System
## Overview
The bot uses a **simplified GitHub API-based update system** that:
- ✅ Downloads latest code as ZIP archive
- ✅ No Git required
- ✅ No merge conflicts
- ✅ Preserves user files automatically
- ✅ Automatic dependency installation
- ✅ TypeScript rebuild
## How It Works
1. **Automatic Updates**: If enabled in `config.jsonc`, the bot checks for updates on every startup
2. **Download**: Latest code is downloaded as ZIP from GitHub
3. **Protection**: User files (accounts, config, sessions) are backed up
4. **Update**: Code files are replaced selectively
5. **Restore**: Protected files are restored
6. **Install**: Dependencies are installed (`npm ci`)
7. **Build**: TypeScript is compiled
8. **Restart**: Bot restarts automatically with new version
## Configuration
In `src/config.jsonc`:
```jsonc
{
"update": {
"enabled": true, // Enable/disable updates
"autoUpdateAccounts": false, // Protect accounts files (recommended: false)
"autoUpdateConfig": false // Protect config.jsonc (recommended: false)
}
}
```
## Protected Files
These files are **always protected** (never overwritten):
- `sessions/` - Browser session data
- `.playwright-chromium-installed` - Browser installation marker
These files are **conditionally protected** (based on config):
- `src/accounts.jsonc` - Protected unless `autoUpdateAccounts: true`
- `src/accounts.json` - Protected unless `autoUpdateAccounts: true`
- `src/config.jsonc` - Protected unless `autoUpdateConfig: true`
## Manual Update
Run the update manually:
```bash
node setup/update/update.mjs
```
## Update Detection
The bot uses marker files to prevent restart loops:
- `.update-happened` - Created when files are actually updated
- `.update-restart-count` - Tracks restart attempts (max 3)
If no updates are available, **no marker is created** and the bot won't restart.
## Troubleshooting
### Updates disabled
```
⚠️ Updates are disabled in config.jsonc
```
→ Set `update.enabled: true` in `src/config.jsonc`
### Download failed
```
❌ Download failed: [error]
```
→ Check your internet connection
→ Verify GitHub is accessible
### Extraction failed
```
❌ Extraction failed: [error]
```
→ Ensure you have one of: `unzip`, `tar`, or PowerShell (Windows)
### Build failed
```
⚠️ Update completed with build warnings
```
→ Check TypeScript errors above
→ May still work, but review errors
## Architecture
### File Structure
```
setup/update/
├── update.mjs # Main update script (468 lines)
└── README.md # This file
```
### Update Flow
```
Start
Check config (enabled?)
Read user preferences (autoUpdate flags)
Backup protected files
Download ZIP from GitHub
Extract archive
Copy files selectively (skip protected)
Restore protected files
Cleanup temporary files
Create marker (.update-happened) if files changed
Install dependencies (npm ci)
Build TypeScript
Exit (bot auto-restarts if marker exists)
```
## Previous System
The old update system (799 lines) supported two methods:
- Git method (required Git, had merge conflicts)
- GitHub API method
**New system**: Only GitHub API method (simpler, more reliable)
## Anti-Loop Protection
The bot has built-in protection against infinite restart loops:
1. **Marker detection**: Bot only restarts if `.update-happened` exists
2. **Restart counter**: Max 3 restart attempts (`.update-restart-count`)
3. **Counter cleanup**: Removed after successful run without updates
4. **No-update detection**: Marker NOT created if already up to date
This ensures the bot never gets stuck in an infinite update loop.
## Dependencies
No external dependencies required! The update system uses only Node.js built-in modules:
- `node:child_process` - Run shell commands
- `node:fs` - File system operations
- `node:https` - Download files
- `node:path` - Path manipulation
## Exit Codes
- `0` - Success (updated or already up to date)
- `1` - Error (download failed, extraction failed, etc.)
## NPM Scripts
- `npm run start` - Start bot (runs update check first if enabled)
- `npm run dev` - Start in dev mode (skips update check)
- `npm run build` - Build TypeScript manually
## Version Info
- Current version: **v2** (GitHub API only)
- Previous version: v1 (Dual Git/GitHub API)
- Lines of code: **468** (down from 799)
- Complexity: **Simple** (down from Complex)

View File

@@ -1,31 +1,25 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* Unified cross-platform setup script for Microsoft Rewards Script V2. * Microsoft Rewards Bot - First-Time Setup Script
* *
* Features: * This script handles initial project setup:
* - Renames accounts.example.jsonc -> accounts.json (idempotent) * 1. Creates accounts.jsonc from template
* - Guides user through account configuration (email, password, TOTP, proxy) * 2. Guides user through account configuration
* - Explains config.jsonc structure and key settings * 3. Installs dependencies (npm install)
* - Installs dependencies (npm install) * 4. Builds TypeScript project (npm run build)
* - Builds TypeScript project (npm run build) * 5. Installs Playwright Chromium browser
* - Installs Playwright Chromium browser (idempotent with marker)
* - Optional immediate start or manual start instructions
* *
* V2 Updates: * IMPORTANT: This script does NOT launch the bot automatically.
* - Enhanced prompts for new config.jsonc structure * After setup, run: npm start
* - Explains humanization, scheduling, notifications
* - References updated documentation (docs/config.md, docs/accounts.md)
* - Improved user guidance for first-time setup
*/ */
import { spawn } from 'child_process';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { spawn } from 'child_process';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
// Project root = two levels up from setup/update directory
const PROJECT_ROOT = path.resolve(__dirname, '..', '..'); const PROJECT_ROOT = path.resolve(__dirname, '..', '..');
const SRC_DIR = path.join(PROJECT_ROOT, 'src'); const SRC_DIR = path.join(PROJECT_ROOT, 'src');
@@ -33,18 +27,23 @@ function log(msg) { console.log(msg); }
function warn(msg) { console.warn(msg); } function warn(msg) { console.warn(msg); }
function error(msg) { console.error(msg); } function error(msg) { console.error(msg); }
function renameAccountsIfNeeded() { function createAccountsFile() {
const accounts = path.join(SRC_DIR, 'accounts.json'); const accounts = path.join(SRC_DIR, 'accounts.jsonc');
const example = path.join(SRC_DIR, 'accounts.example.jsonc'); const example = path.join(SRC_DIR, 'accounts.example.jsonc');
if (fs.existsSync(accounts)) { if (fs.existsSync(accounts)) {
log('accounts.json already exists - skipping rename.'); log('accounts.jsonc already exists - skipping creation');
return; return true;
} }
if (fs.existsSync(example)) { if (fs.existsSync(example)) {
log('Renaming accounts.example.jsonc to accounts.json...'); log('📝 Creating accounts.jsonc from template...');
fs.renameSync(example, accounts); fs.copyFileSync(example, accounts);
log('✓ Created accounts.jsonc');
return false;
} else { } else {
warn('Neither accounts.json nor accounts.example.jsonc found.'); error('❌ Template file accounts.example.jsonc not found!');
return true;
} }
} }
@@ -60,20 +59,25 @@ async function prompt(question) {
}); });
} }
async function loopForAccountsConfirmation() { async function guideAccountConfiguration() {
log('\n📝 Please configure your Microsoft accounts:'); log('\n<EFBFBD> ACCOUNT CONFIGURATION');
log(' - Open: src/accounts.json'); log('════════════════════════════════════════════════════════════');
log(' - Add your email and password for each account'); log('1. Open file: src/accounts.jsonc');
log(' - Optional: Add TOTP secret for 2FA (see docs/accounts.md)'); log('2. Add your Microsoft account credentials:');
log(' - Optional: Configure proxy settings per account'); log(' - email: Your Microsoft account email');
log(' - Save the file (Ctrl+S or Cmd+S)\n'); log(' - password: Your account password');
log(' - totp: (Optional) 2FA secret for automatic authentication');
log('3. Enable accounts by setting "enabled": true');
log('4. Save the file');
log('');
log('📚 Full guide: docs/accounts.md');
log('════════════════════════════════════════════════════════════\n');
// Keep asking until user says yes
for (;;) { for (;;) {
const ans = (await prompt('Have you configured your accounts in accounts.json? (yes/no): ')).toLowerCase(); const ans = (await prompt('Have you configured your accounts? (yes/no): ')).toLowerCase();
if (['yes', 'y'].includes(ans)) break; if (['yes', 'y'].includes(ans)) break;
if (['no', 'n'].includes(ans)) { if (['no', 'n'].includes(ans)) {
log('Please configure accounts.json and save the file, then answer yes.'); log('\n⏸ Please configure src/accounts.jsonc and save it, then answer yes.\n');
continue; continue;
} }
log('Please answer yes or no.'); log('Please answer yes or no.');
@@ -99,64 +103,72 @@ async function ensureNpmAvailable() {
} }
} }
async function startOnly() { async function performSetup() {
log('Starting program (npm run start)...'); log('\n🚀 MICROSOFT REWARDS BOT - FIRST-TIME SETUP');
await ensureNpmAvailable(); log('════════════════════════════════════════════════════════════\n');
// Assume user already installed & built; if dist missing inform user.
const distIndex = path.join(PROJECT_ROOT, 'dist', 'index.js'); // Step 1: Create accounts file
if (!fs.existsSync(distIndex)) { const accountsExisted = createAccountsFile();
warn('Build output not found. Running build first.');
await runCommand(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', 'build']); // Step 2: Guide user through account configuration
await installPlaywrightBrowsers(); if (!accountsExisted) {
await guideAccountConfiguration();
} else { } else {
// Even if build exists, ensure browsers are installed once. log('✓ Using existing accounts.jsonc\n');
await installPlaywrightBrowsers();
} }
await runCommand(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', 'start']);
}
async function fullSetup() {
renameAccountsIfNeeded();
await loopForAccountsConfirmation();
log('\n⚙ Configuration Options (src/config.jsonc):'); // Step 3: Configuration guidance
log(' - browser.headless: Set to true for background operation'); log('\n⚙ CONFIGURATION (src/config.jsonc)');
log(' - execution.clusters: Number of parallel account processes'); log('════════════════════════════════════════════════════════════');
log(' - workers: Enable/disable specific tasks (dailySet, searches, etc.)'); log('Key settings you may want to adjust:');
log(' - humanization: Add natural delays and behavior (recommended: enabled)'); log(' • browser.headless: false = visible browser, true = background');
log(' - schedule: Configure automated daily runs'); log(' • execution.clusters: Number of parallel accounts (default: 1)');
log(' - notifications: Discord webhooks, NTFY push alerts'); log(' • workers: Enable/disable specific tasks');
log(' 📚 Full guide: docs/config.md\n'); log(' • humanization.enabled: Add natural delays (recommended: true)');
log(' • scheduling.enabled: Automate with OS scheduler');
log('');
log('📚 Full configuration guide: docs/getting-started.md');
log('════════════════════════════════════════════════════════════\n');
const reviewConfig = (await prompt('Do you want to review config.jsonc now? (yes/no): ')).toLowerCase(); const reviewConfig = (await prompt('Review config.jsonc before continuing? (yes/no): ')).toLowerCase();
if (['yes', 'y'].includes(reviewConfig)) { if (['yes', 'y'].includes(reviewConfig)) {
log('⏸️ Setup paused. Please review src/config.jsonc, then re-run this setup.'); log('\n⏸️ Setup paused.');
log(' Common settings to check:'); log('Please review and edit src/config.jsonc, then run: npm run setup\n');
log(' - browser.headless (false = visible browser, true = background)');
log(' - execution.runOnZeroPoints (false = skip when no points available)');
log(' - humanization.enabled (true = natural behavior, recommended)');
log(' - schedule.enabled (false = manual runs, true = automated scheduling)');
log('\n After editing config.jsonc, run: npm run setup');
process.exit(0); process.exit(0);
} }
// Step 4: Install dependencies
log('\n📦 Installing dependencies...');
await ensureNpmAvailable(); await ensureNpmAvailable();
await runCommand(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['install']); await runCommand(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['install']);
// Step 5: Build TypeScript
log('\n🔨 Building TypeScript project...');
await runCommand(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', 'build']); await runCommand(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', 'build']);
// Step 6: Install Playwright browsers
await installPlaywrightBrowsers(); await installPlaywrightBrowsers();
log('\n✅ Setup complete!'); // Final message
log(' - Accounts configured: src/accounts.json'); log('\n');
log(' - Configuration: src/config.jsonc'); log('═══════════════════════════════════════════════════════════');
log(' - Documentation: docs/index.md\n'); log('✅ SETUP COMPLETE!');
log('═══════════════════════════════════════════════════════════');
const start = (await prompt('Do you want to start the automation now? (yes/no): ')).toLowerCase(); log('');
if (['yes', 'y'].includes(start)) { log('📁 Configuration files:');
await runCommand(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', 'start']); log(' • Accounts: src/accounts.jsonc');
} else { log(' • Config: src/config.jsonc');
log('\nFinished setup. To start later, run: npm start'); log('');
log('For automated scheduling, use your OS scheduler (see docs/schedule.md).'); log('📚 Documentation:');
} log(' • Getting started: docs/getting-started.md');
log(' • Full docs: docs/index.md');
log('');
log('🚀 TO START THE BOT:');
log(' npm start');
log('');
log('⏰ FOR AUTOMATED SCHEDULING:');
log(' See: docs/getting-started.md (Scheduling section)');
log('═══════════════════════════════════════════════════════════\n');
} }
async function installPlaywrightBrowsers() { async function installPlaywrightBrowsers() {
@@ -178,30 +190,36 @@ async function installPlaywrightBrowsers() {
async function main() { async function main() {
if (!fs.existsSync(SRC_DIR)) { if (!fs.existsSync(SRC_DIR)) {
error('[ERROR] Cannot find src directory at ' + SRC_DIR); error(' Cannot find src directory at ' + SRC_DIR);
process.exit(1); process.exit(1);
} }
process.chdir(PROJECT_ROOT); process.chdir(PROJECT_ROOT);
for (;;) { // Check if already setup (dist exists and accounts configured)
log('============================'); const distExists = fs.existsSync(path.join(PROJECT_ROOT, 'dist', 'index.js'));
log(' Microsoft Rewards Setup '); const accountsExists = fs.existsSync(path.join(SRC_DIR, 'accounts.jsonc'));
log('============================');
log('Select an option:'); if (distExists && accountsExists) {
log(' 1) Start program now (skip setup)'); log('\n⚠ Setup appears to be already complete.');
log(' 2) Full first-time setup'); log(' • Build output: dist/index.js exists');
log(' 3) Exit'); log(' • Accounts: src/accounts.jsonc exists\n');
const choice = (await prompt('Enter choice (1/2/3): ')).trim();
if (choice === '1') { await startOnly(); break; } const rerun = (await prompt('Run setup anyway? (yes/no): ')).toLowerCase();
if (choice === '2') { await fullSetup(); break; } if (!['yes', 'y'].includes(rerun)) {
if (choice === '3') { log('Exiting.'); process.exit(0); } log('\n💡 To start the bot: npm start');
log('\nInvalid choice. Please select 1, 2 or 3.\n'); log('💡 To rebuild: npm run build\n');
process.exit(0);
}
} }
// After completing action, optionally pause if launched by double click on Windows (no TTY detection simple heuristic)
await performSetup();
// Pause if launched by double-click on Windows
if (process.platform === 'win32' && process.stdin.isTTY) { if (process.platform === 'win32' && process.stdin.isTTY) {
log('\nDone. Press Enter to close.'); log('Press Enter to close...');
await prompt(''); await prompt('');
} }
process.exit(0); process.exit(0);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -970,44 +970,79 @@ async function main(): Promise<void> {
const bootstrap = async () => { const bootstrap = async () => {
try { try {
// Check for updates BEFORE initializing and running tasks // Check for updates BEFORE initializing and running tasks
// CRITICAL: Only restart if update script explicitly indicates new version was installed // Anti-loop protection: Track restart attempts
try { const restartCounterPath = path.join(process.cwd(), '.update-restart-count')
const updateResult = await rewardsBot.runAutoUpdate().catch((e) => { let restartCount = 0
log('main', 'UPDATE', `Auto-update check failed: ${e instanceof Error ? e.message : String(e)}`, 'warn') if (fs.existsSync(restartCounterPath)) {
return -1 try {
}) const content = fs.readFileSync(restartCounterPath, 'utf8')
restartCount = parseInt(content, 10) || 0
// FIXED: Only restart on exit code 0 AND if update actually happened } catch {
// The update script returns 0 even when no update is needed, which causes infinite loop restartCount = 0
// Solution: Check for marker file that update script creates when actual update happens }
if (updateResult === 0) { }
const updateMarkerPath = path.join(process.cwd(), '.update-happened')
const updateHappened = fs.existsSync(updateMarkerPath) // If we've restarted too many times (3+), something is wrong - skip update
if (restartCount >= 3) {
if (updateHappened) { log('main', 'UPDATE', '⚠️ Too many restart attempts detected - skipping update to prevent loop', 'warn')
// Remove marker file // Clean up counter file
try { try {
fs.unlinkSync(updateMarkerPath) fs.unlinkSync(restartCounterPath)
} catch { } catch {
// Ignore cleanup errors // Ignore
} }
} else {
log('main', 'UPDATE', '✅ Update successful - restarting with new version...', 'log', 'green') try {
const updateResult = await rewardsBot.runAutoUpdate().catch((e) => {
// Restart the process with the same arguments log('main', 'UPDATE', `Auto-update check failed: ${e instanceof Error ? e.message : String(e)}`, 'warn')
const { spawn } = await import('child_process') return -1
const child = spawn(process.execPath, process.argv.slice(1), { })
detached: true,
stdio: 'inherit' if (updateResult === 0) {
}) const updateMarkerPath = path.join(process.cwd(), '.update-happened')
child.unref() const updateHappened = fs.existsSync(updateMarkerPath)
process.exit(0)
} else { if (updateHappened) {
log('main', 'UPDATE', 'Already up to date, continuing with bot execution') // Remove marker file
} try {
fs.unlinkSync(updateMarkerPath)
} catch {
// Ignore cleanup errors
}
// Increment restart counter
restartCount++
try {
fs.writeFileSync(restartCounterPath, String(restartCount))
} catch {
// Ignore
}
log('main', 'UPDATE', `✅ Update successful - restarting with new version (attempt ${restartCount}/3)...`, 'log', 'green')
// Restart the process with the same arguments
const { spawn } = await import('child_process')
const child = spawn(process.execPath, process.argv.slice(1), {
detached: true,
stdio: 'inherit'
})
child.unref()
process.exit(0)
} else {
log('main', 'UPDATE', 'Already up to date, continuing with bot execution')
// Clean restart counter on successful non-update run
try {
if (fs.existsSync(restartCounterPath)) {
fs.unlinkSync(restartCounterPath)
}
} catch {
// Ignore
}
}
}
} catch (updateError) {
log('main', 'UPDATE', `Update check failed (continuing): ${updateError instanceof Error ? updateError.message : String(updateError)}`, 'warn')
} }
} catch (updateError) {
log('main', 'UPDATE', `Update check failed (continuing): ${updateError instanceof Error ? updateError.message : String(updateError)}`, 'warn')
} }
await rewardsBot.initialize() await rewardsBot.initialize()