mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-10 01:06:17 +00:00
feat: Refactor setup script for improved user guidance and quick launch functionality 3/3
This commit is contained in:
@@ -1,232 +1,288 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* Microsoft Rewards Bot - First-Time Setup Script
|
* Microsoft Rewards Bot - Setup & Quick Launcher
|
||||||
*
|
*
|
||||||
* This script handles initial project setup:
|
* SMART BEHAVIOR:
|
||||||
|
*
|
||||||
|
* First-time setup (if dist/, accounts.jsonc, or node_modules missing):
|
||||||
* 1. Creates accounts.jsonc from template
|
* 1. Creates accounts.jsonc from template
|
||||||
* 2. Guides user through account configuration
|
* 2. Guides user through account configuration
|
||||||
* 3. Installs dependencies (npm install)
|
* 3. Installs dependencies (npm install)
|
||||||
* 4. Builds TypeScript project (npm run build)
|
* 4. Builds TypeScript project (npm run build)
|
||||||
* 5. Installs Playwright Chromium browser
|
* 5. Installs Playwright Chromium browser
|
||||||
|
* 6. Offers to start the bot immediately
|
||||||
*
|
*
|
||||||
* IMPORTANT: This script does NOT launch the bot automatically.
|
* Already configured (all files present):
|
||||||
* After setup, run: npm start
|
* → Detects complete setup
|
||||||
|
* → Offers to start bot directly (npm start)
|
||||||
|
* → User can choose: Start / Re-run setup / Exit
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* npm run setup # Interactive setup/launcher
|
||||||
|
* node scripts/installer/setup.mjs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { spawn } from 'child_process';
|
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'
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename)
|
||||||
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')
|
||||||
|
|
||||||
function log(msg) { console.log(msg); }
|
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 createAccountsFile() {
|
function createAccountsFile() {
|
||||||
const accounts = path.join(SRC_DIR, 'accounts.jsonc');
|
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.jsonc already exists - skipping creation');
|
log('✓ accounts.jsonc already exists - skipping creation')
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fs.existsSync(example)) {
|
if (fs.existsSync(example)) {
|
||||||
log('📝 Creating accounts.jsonc from template...');
|
log('📝 Creating accounts.jsonc from template...')
|
||||||
fs.copyFileSync(example, accounts);
|
fs.copyFileSync(example, accounts)
|
||||||
log('✓ Created accounts.jsonc');
|
log('✓ Created accounts.jsonc')
|
||||||
return false;
|
return false
|
||||||
} else {
|
} else {
|
||||||
error('❌ Template file accounts.example.jsonc not found!');
|
error('❌ Template file accounts.example.jsonc not found!')
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function prompt(question) {
|
async function prompt(question) {
|
||||||
return await new Promise(resolve => {
|
return await new Promise(resolve => {
|
||||||
process.stdout.write(question);
|
process.stdout.write(question)
|
||||||
const onData = (data) => {
|
const onData = (data) => {
|
||||||
const ans = data.toString().trim();
|
const ans = data.toString().trim()
|
||||||
process.stdin.off('data', onData);
|
process.stdin.off('data', onData)
|
||||||
resolve(ans);
|
resolve(ans)
|
||||||
};
|
}
|
||||||
process.stdin.on('data', onData);
|
process.stdin.on('data', onData)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function guideAccountConfiguration() {
|
async function guideAccountConfiguration() {
|
||||||
log('\n<> ACCOUNT CONFIGURATION');
|
log('\n<> ACCOUNT CONFIGURATION')
|
||||||
log('════════════════════════════════════════════════════════════');
|
log('════════════════════════════════════════════════════════════')
|
||||||
log('1. Open file: src/accounts.jsonc');
|
log('1. Open file: src/accounts.jsonc')
|
||||||
log('2. Add your Microsoft account credentials:');
|
log('2. Add your Microsoft account credentials:')
|
||||||
log(' - email: Your Microsoft account email');
|
log(' - email: Your Microsoft account email')
|
||||||
log(' - password: Your account password');
|
log(' - password: Your account password')
|
||||||
log(' - totp: (Optional) 2FA secret for automatic authentication');
|
log(' - totp: (Optional) 2FA secret for automatic authentication')
|
||||||
log('3. Enable accounts by setting "enabled": true');
|
log('3. Enable accounts by setting "enabled": true')
|
||||||
log('4. Save the file');
|
log('4. Save the file')
|
||||||
log('');
|
log('')
|
||||||
log('📚 Full guide: docs/accounts.md');
|
log('📚 Full guide: docs/accounts.md')
|
||||||
log('════════════════════════════════════════════════════════════\n');
|
log('════════════════════════════════════════════════════════════\n')
|
||||||
|
|
||||||
for (;;) {
|
for (; ;) {
|
||||||
const ans = (await prompt('Have you configured your accounts? (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('\n⏸️ Please configure src/accounts.jsonc and save it, then answer yes.\n');
|
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.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function runCommand(cmd, args, opts = {}) {
|
function runCommand(cmd, args, opts = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
log(`Running: ${cmd} ${args.join(' ')}`);
|
log(`Running: ${cmd} ${args.join(' ')}`)
|
||||||
const child = spawn(cmd, args, { stdio: 'inherit', shell: process.platform === 'win32', ...opts });
|
const child = spawn(cmd, args, { stdio: 'inherit', shell: process.platform === 'win32', ...opts })
|
||||||
child.on('exit', (code) => {
|
child.on('exit', (code) => {
|
||||||
if (code === 0) return resolve();
|
if (code === 0) return resolve()
|
||||||
reject(new Error(`${cmd} exited with code ${code}`));
|
reject(new Error(`${cmd} exited with code ${code}`))
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureNpmAvailable() {
|
async function ensureNpmAvailable() {
|
||||||
try {
|
try {
|
||||||
await runCommand(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['-v']);
|
await runCommand(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['-v'])
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error('npm not found in PATH. Install Node.js first.');
|
throw new Error('npm not found in PATH. Install Node.js first.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function performSetup() {
|
async function performSetup() {
|
||||||
log('\n🚀 MICROSOFT REWARDS BOT - FIRST-TIME SETUP');
|
log('\n🚀 MICROSOFT REWARDS BOT - FIRST-TIME SETUP')
|
||||||
log('════════════════════════════════════════════════════════════\n');
|
log('════════════════════════════════════════════════════════════\n')
|
||||||
|
|
||||||
// Step 1: Create accounts file
|
// Step 1: Create accounts file
|
||||||
const accountsExisted = createAccountsFile();
|
const accountsExisted = createAccountsFile()
|
||||||
|
|
||||||
// Step 2: Guide user through account configuration
|
// Step 2: Guide user through account configuration
|
||||||
if (!accountsExisted) {
|
if (!accountsExisted) {
|
||||||
await guideAccountConfiguration();
|
await guideAccountConfiguration()
|
||||||
} else {
|
} else {
|
||||||
log('✓ Using existing accounts.jsonc\n');
|
log('✓ Using existing accounts.jsonc\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3: Configuration guidance
|
// Step 3: Configuration guidance
|
||||||
log('\n⚙️ CONFIGURATION (src/config.jsonc)');
|
log('\n⚙️ CONFIGURATION (src/config.jsonc)')
|
||||||
log('════════════════════════════════════════════════════════════');
|
log('════════════════════════════════════════════════════════════')
|
||||||
log('Key settings you may want to adjust:');
|
log('Key settings you may want to adjust:')
|
||||||
log(' • browser.headless: false = visible browser, true = background');
|
log(' • browser.headless: false = visible browser, true = background')
|
||||||
log(' • execution.clusters: Number of parallel accounts (default: 1)');
|
log(' • execution.clusters: Number of parallel accounts (default: 1)')
|
||||||
log(' • workers: Enable/disable specific tasks');
|
log(' • workers: Enable/disable specific tasks')
|
||||||
log(' • humanization.enabled: Add natural delays (recommended: true)');
|
log(' • humanization.enabled: Add natural delays (recommended: true)')
|
||||||
log(' • scheduling.enabled: Automate with OS scheduler');
|
log(' • scheduling.enabled: Automate with OS scheduler')
|
||||||
log('');
|
log('')
|
||||||
log('📚 Full configuration guide: docs/getting-started.md');
|
log('📚 Full configuration guide: docs/getting-started.md')
|
||||||
log('════════════════════════════════════════════════════════════\n');
|
log('════════════════════════════════════════════════════════════\n')
|
||||||
|
|
||||||
const reviewConfig = (await prompt('Review config.jsonc before continuing? (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('\n⏸️ Setup paused.');
|
log('\n⏸️ Setup paused.')
|
||||||
log('Please review and edit src/config.jsonc, then run: npm run setup\n');
|
log('Please review and edit src/config.jsonc, then run: npm run setup\n')
|
||||||
process.exit(0);
|
process.exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Install dependencies
|
// Step 4: Install dependencies
|
||||||
log('\n📦 Installing 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
|
// Step 5: Build TypeScript
|
||||||
log('\n🔨 Building TypeScript project...');
|
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
|
// Step 6: Install Playwright browsers
|
||||||
await installPlaywrightBrowsers();
|
await installPlaywrightBrowsers()
|
||||||
|
|
||||||
// Final message
|
// Final message
|
||||||
log('\n');
|
log('\n')
|
||||||
log('═══════════════════════════════════════════════════════════');
|
log('═══════════════════════════════════════════════════════════')
|
||||||
log('✅ SETUP COMPLETE!');
|
log('✅ SETUP COMPLETE!')
|
||||||
log('═══════════════════════════════════════════════════════════');
|
log('═══════════════════════════════════════════════════════════')
|
||||||
log('');
|
log('')
|
||||||
log('📁 Configuration files:');
|
log('📁 Configuration files:')
|
||||||
log(' • Accounts: src/accounts.jsonc');
|
log(' • Accounts: src/accounts.jsonc')
|
||||||
log(' • Config: src/config.jsonc');
|
log(' • Config: src/config.jsonc')
|
||||||
log('');
|
log('')
|
||||||
log('📚 Documentation:');
|
log('📚 Documentation:')
|
||||||
log(' • Getting started: docs/getting-started.md');
|
log(' • Getting started: docs/getting-started.md')
|
||||||
log(' • Full docs: docs/index.md');
|
log(' • Full docs: docs/index.md')
|
||||||
log('');
|
log('')
|
||||||
log('🚀 TO START THE BOT:');
|
log('🚀 TO START THE BOT:')
|
||||||
log(' npm start');
|
log(' npm start')
|
||||||
log('');
|
log('')
|
||||||
log('⏰ FOR AUTOMATED SCHEDULING:');
|
log('⏰ FOR AUTOMATED SCHEDULING:')
|
||||||
log(' See: docs/getting-started.md (Scheduling section)');
|
log(' See: docs/getting-started.md (Scheduling section)')
|
||||||
log('═══════════════════════════════════════════════════════════\n');
|
log('═══════════════════════════════════════════════════════════\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function installPlaywrightBrowsers() {
|
async function installPlaywrightBrowsers() {
|
||||||
const PLAYWRIGHT_MARKER = path.join(PROJECT_ROOT, '.playwright-chromium-installed');
|
const PLAYWRIGHT_MARKER = path.join(PROJECT_ROOT, '.playwright-chromium-installed')
|
||||||
// Idempotent: skip if marker exists
|
// Idempotent: skip if marker exists
|
||||||
if (fs.existsSync(PLAYWRIGHT_MARKER)) {
|
if (fs.existsSync(PLAYWRIGHT_MARKER)) {
|
||||||
log('Playwright chromium already installed (marker found).');
|
log('Playwright chromium already installed (marker found).')
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
log('Ensuring Playwright chromium browser is installed...');
|
log('Ensuring Playwright chromium browser is installed...')
|
||||||
try {
|
try {
|
||||||
await runCommand(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['playwright', 'install', 'chromium']);
|
await runCommand(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['playwright', 'install', 'chromium'])
|
||||||
fs.writeFileSync(PLAYWRIGHT_MARKER, new Date().toISOString());
|
fs.writeFileSync(PLAYWRIGHT_MARKER, new Date().toISOString())
|
||||||
log('Playwright chromium install complete.');
|
log('Playwright chromium install complete.')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
warn('Failed to install Playwright chromium automatically. You can manually run: npx playwright install chromium');
|
warn('Failed to install Playwright chromium automatically. You can manually run: npx playwright install chromium')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if setup is complete
|
||||||
|
* @returns {boolean} True if project is already set up
|
||||||
|
*/
|
||||||
|
function isSetupComplete() {
|
||||||
|
const distExists = fs.existsSync(path.join(PROJECT_ROOT, 'dist', 'index.js'))
|
||||||
|
const accountsExists = fs.existsSync(path.join(SRC_DIR, 'accounts.jsonc'))
|
||||||
|
const nodeModulesExists = fs.existsSync(path.join(PROJECT_ROOT, 'node_modules'))
|
||||||
|
|
||||||
|
return distExists && accountsExists && nodeModulesExists
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch the bot directly (for already-configured projects)
|
||||||
|
*/
|
||||||
|
async function launchBot() {
|
||||||
|
log('\n🚀 MICROSOFT REWARDS BOT - QUICK START')
|
||||||
|
log('════════════════════════════════════════════════════════════\n')
|
||||||
|
log('✓ Project already configured')
|
||||||
|
log('✓ Starting bot with npm start...\n')
|
||||||
|
|
||||||
|
try {
|
||||||
|
await runCommand(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['start'])
|
||||||
|
} catch (err) {
|
||||||
|
error('\n❌ Failed to start bot: ' + err.message)
|
||||||
|
log('\n💡 Try running manually: npm start\n')
|
||||||
|
process.exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
if (!fs.existsSync(SRC_DIR)) {
|
if (!fs.existsSync(SRC_DIR)) {
|
||||||
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)
|
||||||
|
|
||||||
// Check if already setup (dist exists and accounts configured)
|
// Check if project is already fully set up
|
||||||
const distExists = fs.existsSync(path.join(PROJECT_ROOT, 'dist', 'index.js'));
|
if (isSetupComplete()) {
|
||||||
const accountsExists = fs.existsSync(path.join(SRC_DIR, 'accounts.jsonc'));
|
log('\n✅ Setup detected as complete!')
|
||||||
|
log(' • Build: dist/index.js ✓')
|
||||||
if (distExists && accountsExists) {
|
log(' • Accounts: src/accounts.jsonc ✓')
|
||||||
log('\n⚠️ Setup appears to be already complete.');
|
log(' • Dependencies: node_modules ✓\n')
|
||||||
log(' • Build output: dist/index.js exists');
|
|
||||||
log(' • Accounts: src/accounts.jsonc exists\n');
|
const choice = (await prompt('What would you like to do?\n [1] Start the bot (npm start)\n [2] Re-run setup\n [3] Exit\nChoice (1-3): ')).trim()
|
||||||
|
|
||||||
const rerun = (await prompt('Run setup anyway? (yes/no): ')).toLowerCase();
|
if (choice === '1' || choice === '') {
|
||||||
if (!['yes', 'y'].includes(rerun)) {
|
await launchBot()
|
||||||
log('\n💡 To start the bot: npm start');
|
process.exit(0)
|
||||||
log('💡 To rebuild: npm run build\n');
|
} else if (choice === '2') {
|
||||||
process.exit(0);
|
log('\n🔄 Re-running full setup...\n')
|
||||||
|
// Continue to performSetup below
|
||||||
|
} else {
|
||||||
|
log('\n👋 Goodbye!\n')
|
||||||
|
process.exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await performSetup();
|
// Perform full setup
|
||||||
|
await performSetup()
|
||||||
|
|
||||||
|
// After setup, ask if user wants to start
|
||||||
|
log('\n🎯 Setup complete! Would you like to start the bot now?')
|
||||||
|
const startNow = (await prompt('Start bot? (yes/no): ')).toLowerCase()
|
||||||
|
|
||||||
|
if (['yes', 'y'].includes(startNow)) {
|
||||||
|
log('\n🚀 Starting bot...\n')
|
||||||
|
await launchBot()
|
||||||
|
} else {
|
||||||
|
log('\n💡 To start the bot later, run: npm start')
|
||||||
|
log('💡 Or re-run this script: npm run setup\n')
|
||||||
|
}
|
||||||
|
|
||||||
// Pause if launched by double-click on Windows
|
// Pause if launched by double-click on Windows
|
||||||
if (process.platform === 'win32' && process.stdin.isTTY) {
|
if (process.platform === 'win32' && process.stdin.isTTY) {
|
||||||
log('Press Enter to close...');
|
log('Press Enter to close...')
|
||||||
await prompt('');
|
await prompt('')
|
||||||
}
|
}
|
||||||
|
|
||||||
process.exit(0);
|
process.exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow clean Ctrl+C
|
// Allow clean Ctrl+C
|
||||||
process.on('SIGINT', () => { console.log('\nInterrupted.'); process.exit(1); });
|
process.on('SIGINT', () => { console.log('\nInterrupted.'); process.exit(1) })
|
||||||
|
|
||||||
main().catch(err => {
|
main().catch(err => {
|
||||||
error('\nSetup failed: ' + err.message);
|
error('\nSetup failed: ' + err.message)
|
||||||
process.exit(1);
|
process.exit(1)
|
||||||
});
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user