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

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