import { execSync, spawn } from 'child_process'; import { publicIpv4 } from 'public-ip'; import dotenv from 'dotenv'; import fs from 'fs'; import os from 'os'; const host = os.hostname(); const args = process.argv.slice(2); const reportWebhook = process.env.REPORT_WEBHOOK; const botConsoleWebhook = process.env.BOT_CONSOLE_WEBHOOK; const botConsoleWebhookName = process.env.BOT_CONSOLE_WEBHOOK_NAME || 'Microsoft Rewards Bot'; const managerWebhook = process.env.MANAGER_WEBHOOK; const managerWebhookName = process.env.MANAGER_WEBHOOK_NAME || 'Microsoft Rewards Bot Manager'; async function logManager(message, type) { if (type === 'info') console.log(`\x1b[34m${message}\x1b[0m`); else if (type === 'error') console.log(`\x1b[31m${message}\x1b[0m`); else console.log(`\x1b[37m${message}\x1b[0m`); if (managerWebhook) { const payload = { content: message, username: managerWebhookName, }; try { await fetch(managerWebhook, { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); } catch (error) { console.error(`[${host}] Error sending webhook message: ${error.message}`); } } } async function logBotConsole(message) { console.log(message); if (botConsoleWebhook) { const payload = { content: message, username: botConsoleWebhookName, }; try { await fetch(botConsoleWebhook, { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); } catch (error) { console.error(`[${host}] Error sending webhook message: ${error.message}`); } } } async function checkUpdate() { try { execSync('git fetch'); const currentHead = execSync('git rev-parse HEAD').toString().trim(); const remoteHead = execSync('git rev-parse "@{u}"').toString().trim(); if (currentHead !== remoteHead) { logManager(`[${host}] Updates available! Please run the updater script.`, 'info'); } } catch (error) { logManager('[${host}] Failed to check for updates.', 'error'); } } async function vpnConnect(vpnName) { if (!vpnName) return logManager(`[${host}] Please provide the VPN name as an argument.`, 'error'); logManager(`[${host}] Disconnecting from VPNs`, 'info'); await vpnDisconnect(); const maxAttempts = process.env.RETRIES; for (let attempt = 1; attempt <= maxAttempts; attempt++) { await execSync(`nmcli connection up "${vpnName}"`); const status = await execSync('nmcli connection show --active').toString().includes(vpnName); if (!status) { logManager(`[${host}] Failed to connect to VPN: ${vpnName}. Retrying (attempt ${attempt} of ${maxAttempts})...`, 'error'); await sleep(1000); } else { await logManager(`[${host}] VPN connection successfully established to ${vpnName} (IP: ${await publicIpv4() || 'Unknown'}).`, 'info'); return 0; } } logManager(`[${host}] Maximum number of connection attempts reached. Failed to connect to VPN: ${vpnName}`, 'error'); await vpnDisconnect(); return 1; } function vpnDisconnect() { const vpnConnection = execSync('nmcli connection show --active | awk \'/vpn/ {print $1}\'').toString().trim(); if (!vpnConnection) { logManager(`[${host}] Successfully disconnected from all VPNs.`, 'info'); return 0; } try { execSync(`nmcli connection down "${vpnConnection}"`); const status = execSync('nmcli connection show --active | awk \'/vpn/ {print $1}\'').toString().trim(); if (!status) { logManager(`[${host}] Successfully disconnected from all VPNs.`, 'info'); return 0; } else { logManager(`[${host}] Failed to disconnect from all VPNs.`, 'error'); return 1; } } catch (error) { logManager(`[${host}] Failed to disconnect from all VPNs.`, 'error'); return 1; } } async function startBot(accountName) { const accountPath = `./accounts/${accountName}.json`; if (fs.existsSync(accountPath)) { let commandSuffix = ''; if (reportWebhook === '1') { commandSuffix += ` --discord ${reportWebhook}`; } if (process.env.BOT_BROWSER) { commandSuffix += ' --browser ' + process.env.BOT_BROWSER; } let commandPrefix = `python -u ./Microsoft-Rewards-bot/ms_rewards_farmer.py --accounts-file ../accounts/${accountName}.json --dont-check-for-updates --shuffle --session --superfast --on-finish exit --no-webdriver-manager --skip-unusual`; logManager(`[${host}] Script started for ${accountName}`); if (isRootUser()) { const containerEnv = fs.readFileSync('/proc/1/environ', 'utf8'); const isLXCContainer = containerEnv.includes('container=lxc') || containerEnv.includes('container=lxc-libvirt'); if (isLXCContainer) commandPrefix += ' --virtual-display'; } return new Promise((resolve, reject) => { const childProcess = spawn('bash', ['-c', `${commandPrefix} ${commandSuffix}`], { stdio: ['pipe', 'pipe', 'pipe'] }); childProcess.stdout.on('data', (data) => { const output = data.toString().trim(); logBotConsole(`[${host}][${accountName}] STDOUT: ${output}`); if (output.includes('Press enter') || output.includes('Press any key')) { setTimeout(() => { childProcess.stdin.write('\n'); }, 1000); } }); childProcess.stderr.on('data', (data) => { const output = data.toString().trim(); logBotConsole(`[${host}][${accountName}] STDERR: ${output}`); if (output.includes('Press enter') || output.includes('Press any key')) { setTimeout(() => { childProcess.stdin.write('\n'); }, 1000); } }); childProcess.on('exit', (code) => { if (code !== 0) { logManager(`[${host}] Bot process for ${accountName} exited with code ${code}. Restarting...`, 'error'); startBot(accountName).then(resolve).catch(reject); } else { const currentDate = new Date(); const formattedDate = `${currentDate.getDate()}-${currentDate.getMonth() + 1}-${currentDate.getFullYear()}`; const logEntry = { accountName: accountName, date: formattedDate }; fs.readFile('batch_logs.json', 'utf8', (err, data) => { if (err) { logManager(`Failed to read batch_logs.json: ${err}`, 'error'); return resolve(); } let logEntries = []; try { logEntries = JSON.parse(data); } catch (parseError) { logManager(`Failed to parse batch_logs.json: ${parseError}`, 'error'); } const existingEntryIndex = logEntries.findIndex(entry => entry.accountName === accountName); if (existingEntryIndex !== -1) logEntries[existingEntryIndex] = logEntry; else logEntries.push(logEntry); fs.writeFile('batch_logs.json', JSON.stringify(logEntries, null, 2), err => { if (err) logManager(`Failed to write to batch_logs.json: ${err}`, 'error'); resolve(); }); }); } }); childProcess.on('close', (code) => { logManager(`[${accountName}] Bot process exited with code ${code}`); }); childProcess.on('error', (err) => { logManager(`[${host}] Failed to start bot for ${accountName}.`, 'error'); reject(err); }); setTimeout(() => { logManager(`[${host}] Bot process for ${accountName} exceeded the timeout. Killing the process...`, 'error'); childProcess.kill(); }, 150 * 60 * 1000); }); } else { logManager(`[${host}] File ${accountPath} does not exist, skipping starting bot for this VPN!`, 'error'); return Promise.resolve({ error: `File ${accountPath} does not exist.` }); } } function sleep(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } function isRootUser() { return process.getuid && process.getuid() === 0; } async function runAll() { await checkUpdate(); const vpns = execSync('nmcli connection show | awk \'/vpn/ {print $1}\'').toString().trim().split('\n'); for (const vpn of vpns) { const currentDate = new Date(); const formattedDate = `${currentDate.getDate()}-${currentDate.getMonth() + 1}-${currentDate.getFullYear()}`; const logEntries = JSON.parse(fs.readFileSync('batch_logs.json', 'utf8')); const entry = logEntries.find(entries => entries.accountName === vpn) || false; if (entry && entry.date === formattedDate) { logManager(`[${host}] ${vpn} already ran today!`, 'info'); continue; } logManager(`[${host}] Switching to VPN: [${vpn}]`, 'info'); const con = await vpnConnect(vpn); if (con) continue; await startBot(vpn); } return 0; } async function runBatch(vpn) { logManager(`[${host}] Connecting to VPN: [${vpn}]`, 'info'); const con = await vpnConnect(vpn); if (con) return 1; await startBot(vpn); return 0; } (async () => { await checkUpdate(); if (fs.existsSync('.env')) { dotenv.config({ path: '.env' }); logManager(`[${host}] Config file: .env.`, 'info'); } else { logManager(`[${host}] Config file: not found.`, 'error'); process.exit(1); } logManager(`[${host}] Bot Console Webhook: ${botConsoleWebhook ? 'True' : 'False'}\n[${host}] Bot Report Webhook: ${reportWebhook ? 'True' : 'False'}\n[${host}] Bot Manager Webhook: ${managerWebhook ? 'True' : 'False'}`); logManager(`[${host}] Starting mcr-bot on host: ${host}`, 'info'); if (!fs.existsSync('batch_logs.json')) { fs.writeFile('batch_logs.json', '[]', err => { if (err) { logManager(`Failed to create batch_logs.json: ${err}`, 'error'); process.exit(1); } }); } else { const fileContent = await fs.readFile('batch_logs.json', 'utf-8'); try { JSON.parse(fileContent); } catch (error) { logManager(`[${host}] Existing log file is not in proper JSON format: ${error}`, 'error'); process.exit(1); } } logManager(`[${host}] Log file: batch_logs.json`, 'info'); for (let i = 0; i < args.length; i++) { if (args[i].startsWith('--')) { if (args[i] === '--accounts' && i + 1 < args.length) { const accounts = args[i + 1]; return await runBatch(accounts); } else if (args[i] === '--all') { const n = true; while (n) { try { await runAll(); await sleep(1000); } catch (error) { logManager(`[${host}] An error occurred: ${error.message}`, 'error'); } } } } } logManager(`[${host}] Missing arguments`); })();