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

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
setlocal
REM Wrapper to run setup via npm (Windows)
REM Navigates to project root and runs npm run setup
setlocal EnableDelayedExpansion
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 PROJECT_ROOT=%SCRIPT_DIR%..
if not exist "%PROJECT_ROOT%\package.json" (
echo [ERROR] package.json not found in project root.
echo.
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
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%"
echo Running setup script via npm...
REM Run setup script
echo Running setup wizard...
echo.
call npm run setup
set EXITCODE=%ERRORLEVEL%
echo.
echo Setup finished with exit code %EXITCODE%.
echo Press Enter to close.
pause >NUL
if %EXITCODE% EQU 0 (
echo ========================================
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%

View File

@@ -1,35 +1,84 @@
#!/usr/bin/env bash
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)"
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
NPM_VERSION="$(npm -v 2>/dev/null || true)"
echo "npm detected: ${NPM_VERSION}"
NPM_VERSION="$(npm -v 2>/dev/null || echo 'unknown')"
echo "[OK] npm detected: v${NPM_VERSION}"
else
echo "[ERROR] npm not detected."
echo " Install Node.js and npm from nodejs.org or your package manager"
echo "[ERROR] npm not found!"
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
fi
if command -v git >/dev/null 2>&1; then
GIT_VERSION="$(git --version 2>/dev/null || true)"
echo "Git detected: ${GIT_VERSION}"
GIT_VERSION="$(git --version 2>/dev/null | cut -d' ' -f3)"
echo "[OK] Git detected: v${GIT_VERSION}"
else
echo "[WARN] Git not detected."
echo " Install (Linux): e.g. 'sudo apt install git' (or your distro equivalent)."
echo "[WARN] Git not detected (optional for setup, required for updates)"
echo " • Ubuntu/Debian: sudo apt install git"
echo " • macOS: brew install git"
echo " • Fedora: sudo dnf install git"
fi
if [ ! -f "${PROJECT_ROOT}/package.json" ]; then
echo ""
echo "[ERROR] package.json not found at ${PROJECT_ROOT}" >&2
exit 1
fi
echo
echo "=== Running setup script via npm ==="
echo ""
echo "=== Running Setup Wizard ==="
echo ""
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
/**
* Unified cross-platform setup script for Microsoft Rewards Script V2.
* Microsoft Rewards Bot - First-Time Setup Script
*
* Features:
* - Renames accounts.example.jsonc -> accounts.json (idempotent)
* - Guides user through account configuration (email, password, TOTP, proxy)
* - Explains config.jsonc structure and key settings
* - Installs dependencies (npm install)
* - Builds TypeScript project (npm run build)
* - Installs Playwright Chromium browser (idempotent with marker)
* - Optional immediate start or manual start instructions
* This script handles initial project setup:
* 1. Creates accounts.jsonc from template
* 2. Guides user through account configuration
* 3. Installs dependencies (npm install)
* 4. Builds TypeScript project (npm run build)
* 5. Installs Playwright Chromium browser
*
* V2 Updates:
* - Enhanced prompts for new config.jsonc structure
* - Explains humanization, scheduling, notifications
* - References updated documentation (docs/config.md, docs/accounts.md)
* - Improved user guidance for first-time setup
* IMPORTANT: This script does NOT launch the bot automatically.
* After setup, run: npm start
*/
import { spawn } from 'child_process';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { spawn } from 'child_process';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Project root = two levels up from setup/update directory
const PROJECT_ROOT = path.resolve(__dirname, '..', '..');
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 error(msg) { console.error(msg); }
function renameAccountsIfNeeded() {
const accounts = path.join(SRC_DIR, 'accounts.json');
function createAccountsFile() {
const accounts = path.join(SRC_DIR, 'accounts.jsonc');
const example = path.join(SRC_DIR, 'accounts.example.jsonc');
if (fs.existsSync(accounts)) {
log('accounts.json already exists - skipping rename.');
return;
log('accounts.jsonc already exists - skipping creation');
return true;
}
if (fs.existsSync(example)) {
log('Renaming accounts.example.jsonc to accounts.json...');
fs.renameSync(example, accounts);
log('📝 Creating accounts.jsonc from template...');
fs.copyFileSync(example, accounts);
log('✓ Created accounts.jsonc');
return false;
} 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() {
log('\n📝 Please configure your Microsoft accounts:');
log(' - Open: src/accounts.json');
log(' - Add your email and password for each account');
log(' - Optional: Add TOTP secret for 2FA (see docs/accounts.md)');
log(' - Optional: Configure proxy settings per account');
log(' - Save the file (Ctrl+S or Cmd+S)\n');
async function guideAccountConfiguration() {
log('\n<EFBFBD> ACCOUNT CONFIGURATION');
log('════════════════════════════════════════════════════════════');
log('1. Open file: src/accounts.jsonc');
log('2. Add your Microsoft account credentials:');
log(' - email: Your Microsoft account email');
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 (;;) {
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 (['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;
}
log('Please answer yes or no.');
@@ -99,64 +103,72 @@ async function ensureNpmAvailable() {
}
}
async function startOnly() {
log('Starting program (npm run start)...');
await ensureNpmAvailable();
// Assume user already installed & built; if dist missing inform user.
const distIndex = path.join(PROJECT_ROOT, 'dist', 'index.js');
if (!fs.existsSync(distIndex)) {
warn('Build output not found. Running build first.');
await runCommand(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', 'build']);
await installPlaywrightBrowsers();
async function performSetup() {
log('\n🚀 MICROSOFT REWARDS BOT - FIRST-TIME SETUP');
log('════════════════════════════════════════════════════════════\n');
// Step 1: Create accounts file
const accountsExisted = createAccountsFile();
// Step 2: Guide user through account configuration
if (!accountsExisted) {
await guideAccountConfiguration();
} else {
// Even if build exists, ensure browsers are installed once.
await installPlaywrightBrowsers();
log('✓ Using existing accounts.jsonc\n');
}
await runCommand(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', 'start']);
}
async function fullSetup() {
renameAccountsIfNeeded();
await loopForAccountsConfirmation();
log('\n⚙ Configuration Options (src/config.jsonc):');
log(' - browser.headless: Set to true for background operation');
log(' - execution.clusters: Number of parallel account processes');
log(' - workers: Enable/disable specific tasks (dailySet, searches, etc.)');
log(' - humanization: Add natural delays and behavior (recommended: enabled)');
log(' - schedule: Configure automated daily runs');
log(' - notifications: Discord webhooks, NTFY push alerts');
log(' 📚 Full guide: docs/config.md\n');
// Step 3: Configuration guidance
log('\n⚙ CONFIGURATION (src/config.jsonc)');
log('════════════════════════════════════════════════════════════');
log('Key settings you may want to adjust:');
log(' • browser.headless: false = visible browser, true = background');
log(' • execution.clusters: Number of parallel accounts (default: 1)');
log(' • workers: Enable/disable specific tasks');
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)) {
log('⏸️ Setup paused. Please review src/config.jsonc, then re-run this setup.');
log(' Common settings to check:');
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');
log('\n⏸️ Setup paused.');
log('Please review and edit src/config.jsonc, then run: npm run setup\n');
process.exit(0);
}
// Step 4: Install dependencies
log('\n📦 Installing dependencies...');
await ensureNpmAvailable();
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']);
// Step 6: Install Playwright browsers
await installPlaywrightBrowsers();
log('\n✅ Setup complete!');
log(' - Accounts configured: src/accounts.json');
log(' - Configuration: src/config.jsonc');
log(' - Documentation: docs/index.md\n');
const start = (await prompt('Do you want to start the automation now? (yes/no): ')).toLowerCase();
if (['yes', 'y'].includes(start)) {
await runCommand(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', 'start']);
} else {
log('\nFinished setup. To start later, run: npm start');
log('For automated scheduling, use your OS scheduler (see docs/schedule.md).');
}
// Final message
log('\n');
log('═══════════════════════════════════════════════════════════');
log('✅ SETUP COMPLETE!');
log('═══════════════════════════════════════════════════════════');
log('');
log('📁 Configuration files:');
log(' • Accounts: src/accounts.jsonc');
log(' • Config: src/config.jsonc');
log('');
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() {
@@ -178,30 +190,36 @@ async function installPlaywrightBrowsers() {
async function main() {
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.chdir(PROJECT_ROOT);
for (;;) {
log('============================');
log(' Microsoft Rewards Setup ');
log('============================');
log('Select an option:');
log(' 1) Start program now (skip setup)');
log(' 2) Full first-time setup');
log(' 3) Exit');
const choice = (await prompt('Enter choice (1/2/3): ')).trim();
if (choice === '1') { await startOnly(); break; }
if (choice === '2') { await fullSetup(); break; }
if (choice === '3') { log('Exiting.'); process.exit(0); }
log('\nInvalid choice. Please select 1, 2 or 3.\n');
// Check if already setup (dist exists and accounts configured)
const distExists = fs.existsSync(path.join(PROJECT_ROOT, 'dist', 'index.js'));
const accountsExists = fs.existsSync(path.join(SRC_DIR, 'accounts.jsonc'));
if (distExists && accountsExists) {
log('\n⚠ Setup appears to be already complete.');
log(' • Build output: dist/index.js exists');
log(' • Accounts: src/accounts.jsonc exists\n');
const rerun = (await prompt('Run setup anyway? (yes/no): ')).toLowerCase();
if (!['yes', 'y'].includes(rerun)) {
log('\n💡 To start the bot: npm start');
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) {
log('\nDone. Press Enter to close.');
log('Press Enter to close...');
await prompt('');
}
process.exit(0);
}

File diff suppressed because it is too large Load Diff