Small update to be deployed quickly. (#358)

* chore: Update TypeScript configuration and add @types/node as a dev dependency

* feat: Add unified cross-platform setup script for easier configuration and installation

* docs: Revise README for improved setup instructions and clarity

* feat: Enhance setup scripts with improved prerequisite checks and user prompts

* feat: Refactor setup scripts and enhance browser handling with automatic Playwright installation
This commit is contained in:
Light
2025-09-16 09:34:49 +02:00
committed by GitHub
parent b66114d4dd
commit 02160a07d9
11 changed files with 480 additions and 285 deletions

273
README.md
View File

@@ -1,122 +1,191 @@
# Microsoft-Rewards-Script
Automated Microsoft Rewards script, however this time using TypeScript, Cheerio and Playwright.
Automated Microsoft Rewards script built with TypeScript, Cheerio and Playwright.
Under development, however mainly for personal use!
## How to setup ##
1. Download or clone source code
2. Change `accounts.example.json` to `accounts.json` and add your account details
3. Change `config.json` to your liking
4. Either go the nix or non-nix route
---
### How to setup (not with nix) ###
5. Run `npm i` to install the packages
6. Run `npm run build` to build the script
7. Run `npm run start` to start the built script
## 🚀 Quick Setup (Recommended)
### How to setup (with nix) ##
5. Get [Nix](https://nixos.org/)
6. Run `./run.sh`
7. That's it!
**The easiest way to get started - just download and run!**
## Notes ##
- If you end the script without closing the browser window first (only with headless as false), you'll be left with hanging chrome instances using resources. Use taskmanager to kill these or use the included `npm run kill-chrome-win` script. (Windows)
- If you automate this script, set it to run at least 2 times a day to make sure it picked up all tasks, set `"runOnZeroPoints": false` so it doesn't run when no points are found.
1. **Download or clone** the source code
2. **Run the setup script:**
## Docker (Experimental) ##
### **Before Starting**
**Windows:** Double-click `setup/setup.bat` or run it from command line
- If you had previously built and run the script locally, **remove** the `/node_modules` and `/dist` folders from your `Microsoft-Rewards-Script` directory.
- If you had used Docker with an older version of the script (e.g., 1.4), **remove** any persistently saved `config.json` and session folders. Old `accounts.json` files can be reused.
**Linux/macOS/WSL:** `bash setup/setup.sh`
### **Setup the Source Files**
**Alternative (any platform):** `npm run setup`
1. **Download the Source Code**
3. **Follow the prompts:** The setup script will automatically:
- Rename `accounts.example.json` to `accounts.json`
- Ask you to enter your Microsoft account credentials
- Remind you to review configuration options in `config.json`
- Install all dependencies (`npm install`)
- Build the project (`npm run build`)
- Optionally start the script immediately
2. **Update `accounts.json`**
**That's it!** The setup script handles everything for you.
3. **Edit `config.json`,** ensuring "headless": true, other settings are up to your preference
---
### **Customize the `compose.yaml` File**
## ⚙️ Advanced Setup Options
A basic docker `compose.yaml` is provided. Follow these steps to configure and run the container:
### Nix Users
1. Get [Nix](https://nixos.org/)
2. Run `./run.sh`
3. Done!
1. **Set Your Timezone:** Adjust the `TZ` variable to ensure correct scheduling.
3. **Customize the Schedule:**
- Modify `CRON_SCHEDULE` to set run times. Use [crontab.guru](https://crontab.guru) for help.
- **Note:** The container adds 550 minutes of random variability to each scheduled start time. This can be optionally disabled or customized in the compose file.
4. **(Optional) Run on Startup:**
- Set `RUN_ON_START=true` to execute the script immediately when the container starts.
5. **Start the Container:** Run `docker compose up -d` to build and launch.
6. **Monitor Logs:** Use `docker logs microsoft-rewards-script` to view script execution and to retrieve 'passwordless' login codes.
### Manual Setup (Troubleshooting)
If the automatic setup script doesn't work for your environment:
1. Manually rename `src/accounts.example.json` to `src/accounts.json`
2. Add your Microsoft account details to `accounts.json`
3. Customize `src/config.json` to your preferences
4. Install dependencies: `npm install`
5. Build the project: `npm run build`
6. Start the script: `npm run start`---
## Config ##
| Setting | Description | Default |
| :------------- |:-------------| :-----|
| baseURL | MS Rewards page | `https://rewards.bing.com` |
| sessionPath | Path to where you want sessions/fingerprints to be stored | `sessions` (In ./browser/sessions) |
| headless | If the browser window should be visible be ran in the background | `false` (Browser is visible) |
| parallel | If you want mobile and desktop tasks to run parallel or sequential| `true` |
| runOnZeroPoints | Run the rest of the script if 0 points can be earned | `false` (Will not run on 0 points) |
| clusters | Amount of instances ran on launch, 1 per account | `1` (Will run 1 account at the time) |
| saveFingerprint.mobile | Re-use the same fingerprint each time | `false` (Will generate a new fingerprint each time) |
| saveFingerprint.desktop | Re-use the same fingerprint each time | `false` (Will generate a new fingerprint each time) |
| workers.doDailySet | Complete daily set items | `true` |
| workers.doMorePromotions | Complete promotional items | `true` |
| workers.doPunchCards | Complete punchcards | `true` |
| workers.doDesktopSearch | Complete daily desktop searches | `true` |
| workers.doMobileSearch | Complete daily mobile searches | `true` |
| workers.doDailyCheckIn | Complete daily check-in activity | `true` |
| workers.doReadToEarn | Complete read to earn activity | `true` |
| searchOnBingLocalQueries | Complete the activity "search on Bing" using the `queries.json` or fetched from this repo | `false` (Will fetch from this repo) |
| globalTimeout | The length before the action gets timeout | `30s` |
| searchSettings.useGeoLocaleQueries | Generate search queries based on your geo-location | `false` (Uses EN-US generated queries) |
| searchSettings.scrollRandomResults | Scroll randomly in search results | `true` |
| searchSettings.clickRandomResults | Visit random website from search result| `true` |
| searchSettings.searchDelay | Minimum and maximum time in milliseconds between search queries | `min: 3min` `max: 5min` |
| searchSettings.retryMobileSearchAmount | Keep retrying mobile searches for specified amount | `2` |
| logExcludeFunc | Functions to exclude out of the logs and webhooks | `SEARCH-CLOSE-TABS` |
| webhookLogExcludeFunc | Functions to exclude out of the webhooks log | `SEARCH-CLOSE-TABS` |
| proxy.proxyGoogleTrends | Enable or disable proxying the request via set proxy | `true` (will be proxied) |
| proxy.proxyBingTerms | Enable or disable proxying the request via set proxy | `true` (will be proxied) |
| webhook.enabled | Enable or disable your set webhook | `false` |
| webhook.url | Your Discord webhook URL | `null` |
| conclusionWebhook.enabled | Enable or disable the final summary dedicated webhook | `false` |
| conclusionWebhook.url | Discord webhook URL used ONLY for the end summary | `null` |
## 🐳 Docker Setup (Experimental)
## Features ##
- [x] Multi-Account Support
- [x] Session Storing
- [x] 2FA Support
- [x] Passwordless Support
- [x] Headless Support
- [x] Discord Webhook Support
- [x] Final Summary Webhook (dedicated optional)
- [x] Desktop Searches
- [x] Configurable Tasks
- [x] Microsoft Edge Searches
- [x] Mobile Searches
- [x] Emulated Scrolling Support
- [x] Emulated Link Clicking Support
- [x] Geo Locale Search Queries
- [x] Completing Daily Set
- [x] Completing More Promotions
- [x] Solving Quiz (10 point variant)
- [x] Solving Quiz (30-40 point variant)
- [x] Completing Click Rewards
- [x] Completing Polls
- [x] Completing Punchcards
- [x] Solving This Or That Quiz (Random)
- [x] Solving ABC Quiz
- [x] Completing Daily Check In
- [x] Completing Read To Earn
- [x] Clustering Support
- [x] Proxy Support
- [x] Docker Support (experimental)
- [x] Automatic scheduling (via Docker)
For automated scheduling and containerized deployment.
## Disclaimer ##
Your account may be at risk of getting banned or suspended using this script, you've been warned!
<br />
Use this script at your own risk!
### Before Starting
- Remove `/node_modules` and `/dist` folders if you previously built locally
- Remove old Docker volumes if upgrading from version 1.4 or earlier
- Old `accounts.json` files can be reused
### Quick Docker Setup
1. **Download source code** and configure `accounts.json`
2. **Edit `config.json`** - ensure `"headless": true`
3. **Customize `compose.yaml`:**
- Set your timezone (`TZ` variable)
- Configure schedule (`CRON_SCHEDULE`) - use [crontab.guru](https://crontab.guru) for help
- Optional: Set `RUN_ON_START=true` for immediate execution
4. **Start container:** `docker compose up -d`
5. **Monitor logs:** `docker logs microsoft-rewards-script`
**Note:** The container adds 550 minutes random delay to scheduled runs for more natural behavior.
---
## 📋 Usage Notes
- **Browser Instances:** If you stop the script without closing browser windows (headless=false), use Task Manager or `npm run kill-chrome-win` to clean up
- **Automation Scheduling:** Run at least twice daily, set `"runOnZeroPoints": false` to skip when no points available
- **Multiple Accounts:** The script supports clustering - configure `clusters` in `config.json`
---
## ⚙️ Configuration Reference
Customize behavior by editing `src/config.json`:
### Core Settings
| Setting | Description | Default |
|---------|-------------|---------|
| `baseURL` | Microsoft Rewards page URL | `https://rewards.bing.com` |
| `sessionPath` | Session/fingerprint storage location | `sessions` |
| `headless` | Run browser in background | `false` (visible) |
| `parallel` | Run mobile/desktop tasks simultaneously | `true` |
| `runOnZeroPoints` | Continue when no points available | `false` |
| `clusters` | Number of concurrent account instances | `1` |
### Fingerprint Settings
| Setting | Description | Default |
|---------|-------------|---------|
| `saveFingerprint.mobile` | Reuse mobile browser fingerprint | `false` |
| `saveFingerprint.desktop` | Reuse desktop browser fingerprint | `false` |
### Task Settings
| Setting | Description | Default |
|---------|-------------|---------|
| `workers.doDailySet` | Complete daily set activities | `true` |
| `workers.doMorePromotions` | Complete promotional offers | `true` |
| `workers.doPunchCards` | Complete punchcard activities | `true` |
| `workers.doDesktopSearch` | Perform desktop searches | `true` |
| `workers.doMobileSearch` | Perform mobile searches | `true` |
| `workers.doDailyCheckIn` | Complete daily check-in | `true` |
| `workers.doReadToEarn` | Complete read-to-earn activities | `true` |
### Search Settings
| Setting | Description | Default |
|---------|-------------|---------|
| `searchOnBingLocalQueries` | Use local queries vs. fetched | `false` |
| `searchSettings.useGeoLocaleQueries` | Generate location-based queries | `false` |
| `searchSettings.scrollRandomResults` | Randomly scroll search results | `true` |
| `searchSettings.clickRandomResults` | Click random result links | `true` |
| `searchSettings.searchDelay` | Delay between searches (min/max) | `3-5 minutes` |
| `searchSettings.retryMobileSearchAmount` | Mobile search retry attempts | `2` |
### Advanced Settings
| Setting | Description | Default |
|---------|-------------|---------|
| `globalTimeout` | Action timeout duration | `30s` |
| `logExcludeFunc` | Functions to exclude from logs | `SEARCH-CLOSE-TABS` |
| `webhookLogExcludeFunc` | Functions to exclude from webhooks | `SEARCH-CLOSE-TABS` |
| `proxy.proxyGoogleTrends` | Proxy Google Trends requests | `true` |
| `proxy.proxyBingTerms` | Proxy Bing Terms requests | `true` |
### Webhook Settings
| Setting | Description | Default |
|---------|-------------|---------|
| `webhook.enabled` | Enable Discord notifications | `false` |
| `webhook.url` | Discord webhook URL | `null` |
| `conclusionWebhook.enabled` | Enable summary-only webhook | `false` |
| `conclusionWebhook.url` | Summary webhook URL | `null` |
---
## ✨ Features
**Account Management:**
- ✅ Multi-Account Support
- ✅ Session Storage & Persistence
- ✅ 2FA Support
- ✅ Passwordless Login Support
**Automation & Control:**
- ✅ Headless Browser Operation
- ✅ Clustering Support (Multiple accounts simultaneously)
- ✅ Configurable Task Selection
- ✅ Proxy Support
- ✅ Automatic Scheduling (Docker)
**Search & Activities:**
- ✅ Desktop & Mobile Searches
- ✅ Microsoft Edge Search Simulation
- ✅ Geo-Located Search Queries
- ✅ Emulated Scrolling & Link Clicking
- ✅ Daily Set Completion
- ✅ Promotional Activities
- ✅ Punchcard Completion
- ✅ Daily Check-in
- ✅ Read to Earn Activities
**Quiz & Interactive Content:**
- ✅ Quiz Solving (10 & 30-40 point variants)
- ✅ This Or That Quiz (Random answers)
- ✅ ABC Quiz Solving
- ✅ Poll Completion
- ✅ Click Rewards
**Notifications & Monitoring:**
- ✅ Discord Webhook Integration
- ✅ Dedicated Summary Webhook
- ✅ Comprehensive Logging
- ✅ Docker Support with Monitoring
---
## ⚠️ Disclaimer
**Use at your own risk!** Your Microsoft Rewards account may be suspended or banned when using automation scripts.
This script is provided for educational purposes. The authors are not responsible for any account actions taken by Microsoft.
---
## 🤝 Contributing
This project is primarily for personal use but contributions are welcome. Please ensure any changes maintain compatibility with the existing configuration system.

View File

@@ -12,6 +12,7 @@
"start": "node ./dist/index.js",
"ts-start": "ts-node ./src/index.ts",
"dev": "ts-node ./src/index.ts -dev",
"setup": "node ./setup/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 }\"",
"create-docker": "docker build -t microsoft-rewards-script-docker ."
},
@@ -27,6 +28,7 @@
"author": "Netsky",
"license": "ISC",
"devDependencies": {
"@types/node": "^20.14.11",
"@types/ms": "^0.7.34",
"@typescript-eslint/eslint-plugin": "^7.17.0",
"eslint": "^8.57.0",

22
setup/setup.bat Normal file
View File

@@ -0,0 +1,22 @@
@echo off
setlocal
REM Lightweight wrapper to run setup.mjs without prereq detection (Windows)
REM Assumes Node is already installed and available in PATH.
set SCRIPT_DIR=%~dp0
set SETUP_FILE=%SCRIPT_DIR%setup.mjs
if not exist "%SETUP_FILE%" (
echo [ERROR] setup.mjs not found next to this batch file.
pause
exit /b 1
)
echo Running setup script...
node "%SETUP_FILE%"
set EXITCODE=%ERRORLEVEL%
echo.
echo Setup finished with exit code %EXITCODE%.
echo Press Enter to close.
pause >NUL
exit /b %EXITCODE%

171
setup/setup.mjs Normal file
View File

@@ -0,0 +1,171 @@
#!/usr/bin/env node
/**
* Unified cross-platform setup script for Microsoft Rewards Script.
* Handles:
* - Renaming accounts.example.json -> accounts.json (idempotent)
* - Prompt loop to confirm passwords added
* - Inform about config.json and conclusionWebhook
* - Run npm install + npm run build
* - Optional start
*/
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 = parent of this setup directory
const PROJECT_ROOT = path.resolve(__dirname, '..');
const SRC_DIR = path.join(PROJECT_ROOT, 'src');
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');
const example = path.join(SRC_DIR, 'accounts.example.json');
if (fs.existsSync(accounts)) {
log('accounts.json already exists - skipping rename.');
return;
}
if (fs.existsSync(example)) {
log('Renaming accounts.example.json to accounts.json...');
fs.renameSync(example, accounts);
} else {
warn('Neither accounts.json nor accounts.example.json found.');
}
}
async function prompt(question) {
return await new Promise(resolve => {
process.stdout.write(question);
const onData = (data) => {
const ans = data.toString().trim();
process.stdin.off('data', onData);
resolve(ans);
};
process.stdin.on('data', onData);
});
}
async function loopForAccountsConfirmation() {
// Keep asking until user says yes
for (;;) {
const ans = (await prompt('Have you entered your passwords in accounts.json? (yes/no) : ')).toLowerCase();
if (['yes', 'y'].includes(ans)) break;
if (['no', 'n'].includes(ans)) {
log('Please enter your passwords in accounts.json and save the file (Ctrl+S), then answer yes.');
continue;
}
log('Please answer yes or no.');
}
}
function runCommand(cmd, args, opts = {}) {
return new Promise((resolve, reject) => {
log(`Running: ${cmd} ${args.join(' ')}`);
const child = spawn(cmd, args, { stdio: 'inherit', shell: process.platform === 'win32', ...opts });
child.on('exit', (code) => {
if (code === 0) return resolve();
reject(new Error(`${cmd} exited with code ${code}`));
});
});
}
async function ensureNpmAvailable() {
try {
await runCommand(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['-v']);
} catch (e) {
throw new Error('npm not found in PATH. Install Node.js first.');
}
}
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();
} else {
// Even if build exists, ensure browsers are installed once.
await installPlaywrightBrowsers();
}
await runCommand(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', 'start']);
}
async function fullSetup() {
renameAccountsIfNeeded();
await loopForAccountsConfirmation();
log('\nYou can now review config.json (same folder) to adjust settings such as conclusionWebhook.');
log('(How to enable it is documented in the repository README.)\n');
await ensureNpmAvailable();
await runCommand(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['install']);
await runCommand(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', 'build']);
await installPlaywrightBrowsers();
const start = (await prompt('Do you want to start the program now? (yes/no) : ')).toLowerCase();
if (['yes', 'y'].includes(start)) {
await runCommand(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', 'start']);
} else {
log('Finished setup without starting.');
}
}
async function installPlaywrightBrowsers() {
const PLAYWRIGHT_MARKER = path.join(PROJECT_ROOT, '.playwright-chromium-installed');
// Idempotent: skip if marker exists
if (fs.existsSync(PLAYWRIGHT_MARKER)) {
log('Playwright chromium already installed (marker found).');
return;
}
log('Ensuring Playwright chromium browser is installed...');
try {
await runCommand(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['playwright', 'install', 'chromium']);
fs.writeFileSync(PLAYWRIGHT_MARKER, new Date().toISOString());
log('Playwright chromium install complete.');
} catch (e) {
warn('Failed to install Playwright chromium automatically. You can manually run: npx playwright install chromium');
}
}
async function main() {
if (!fs.existsSync(SRC_DIR)) {
error('[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');
}
// After completing action, optionally pause if launched by double click on Windows (no TTY detection simple heuristic)
if (process.platform === 'win32' && process.stdin.isTTY) {
log('\nDone. Press Enter to close.');
await prompt('');
}
process.exit(0);
}
// Allow clean Ctrl+C
process.on('SIGINT', () => { console.log('\nInterrupted.'); process.exit(1); });
main().catch(err => {
error('\nSetup failed: ' + err.message);
process.exit(1);
});

41
setup/setup.sh Normal file
View File

@@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail
# Wrapper to run unified Node setup script (setup/setup.mjs) regardless of CWD.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SETUP_FILE="${SCRIPT_DIR}/setup.mjs"
echo "=== Prerequisite Check ==="
if command -v node >/dev/null 2>&1; then
NODE_VERSION="$(node -v 2>/dev/null || true)"
echo "Node detected: ${NODE_VERSION}"
else
echo "[WARN] Node.js not detected."
echo " Install (Linux): use your package manager (e.g. 'sudo apt install nodejs npm' or install from nodejs.org for latest)."
fi
if command -v git >/dev/null 2>&1; then
GIT_VERSION="$(git --version 2>/dev/null || true)"
echo "Git detected: ${GIT_VERSION}"
else
echo "[WARN] Git not detected."
echo " Install (Linux): e.g. 'sudo apt install git' (or your distro equivalent)."
fi
if [ -z "${NODE_VERSION:-}" ]; then
read -r -p "Continue anyway? (yes/no) : " CONTINUE
case "${CONTINUE,,}" in
yes|y) ;;
*) echo "Aborting. Install prerequisites then re-run."; exit 1;;
esac
fi
if [ ! -f "${SETUP_FILE}" ]; then
echo "[ERROR] setup.mjs not found at ${SETUP_FILE}" >&2
exit 1
fi
echo
echo "=== Running setup script ==="
exec node "${SETUP_FILE}"

View File

@@ -25,19 +25,40 @@ class Browser {
}
async createBrowser(proxy: AccountProxy, email: string): Promise<BrowserContext> {
const browser = await playwright.chromium.launch({
//channel: 'msedge', // Uses Edge instead of chrome
headless: this.bot.config.headless,
...(proxy.url && { proxy: { username: proxy.username, password: proxy.password, server: `${proxy.url}:${proxy.port}` } }),
args: [
'--no-sandbox',
'--mute-audio',
'--disable-setuid-sandbox',
'--ignore-certificate-errors',
'--ignore-certificate-errors-spki-list',
'--ignore-ssl-errors'
]
})
// Optional automatic browser installation (set AUTO_INSTALL_BROWSERS=1)
if (process.env.AUTO_INSTALL_BROWSERS === '1') {
try {
// Dynamically import child_process to avoid overhead otherwise
const { execSync } = await import('child_process') as any
execSync('npx playwright install chromium', { stdio: 'ignore' })
} catch { /* silent */ }
}
let browser: any
try {
browser = await playwright.chromium.launch({
//channel: 'msedge', // Uses Edge instead of chrome
headless: this.bot.config.headless,
...(proxy.url && { proxy: { username: proxy.username, password: proxy.password, server: `${proxy.url}:${proxy.port}` } }),
args: [
'--no-sandbox',
'--mute-audio',
'--disable-setuid-sandbox',
'--ignore-certificate-errors',
'--ignore-certificate-errors-spki-list',
'--ignore-ssl-errors'
]
})
} catch (e: any) {
const msg = (e instanceof Error ? e.message : String(e))
// Common missing browser executable guidance
if (/Executable doesn't exist/i.test(msg)) {
this.bot.log(this.bot.isMobile, 'BROWSER', 'Chromium not installed for Playwright. Run: "npx playwright install chromium" (or set AUTO_INSTALL_BROWSERS=1 to auto attempt).', 'error')
} else {
this.bot.log(this.bot.isMobile, 'BROWSER', 'Failed to launch browser: ' + msg, 'error')
}
throw e
}
const sessionData = await loadSessionData(this.bot.config.sessionPath, email, this.bot.isMobile, this.bot.config.saveFingerprint)

View File

@@ -170,14 +170,28 @@ export class MicrosoftRewardsBot {
const errors: string[] = []
this.axios = new Axios(account.proxy)
const verbose = process.env.DEBUG_REWARDS_VERBOSE === '1'
const formatFullErr = (label: string, e: any) => {
const base = shortErr(e)
if (verbose && e instanceof Error) {
return `${label}:${base} :: ${e.stack?.split('\n').slice(0,4).join(' | ')}`
}
return `${label}:${base}`
}
if (this.config.parallel) {
const mobileInstance = new MicrosoftRewardsBot(true)
mobileInstance.axios = this.axios
// Run both and capture results
const [desktopResult, mobileResult] = await Promise.all([
this.Desktop(account).catch(e => { errors.push(`desktop:${shortErr(e)}`); return null }),
mobileInstance.Mobile(account).catch(e => { errors.push(`mobile:${shortErr(e)}`); return null })
])
// Run both and capture results with detailed logging
const desktopPromise = this.Desktop(account).catch(e => {
log(false, 'TASK', `Desktop flow failed early for ${account.email}: ${e instanceof Error ? e.message : e}`,'error')
errors.push(formatFullErr('desktop', e)); return null
})
const mobilePromise = mobileInstance.Mobile(account).catch(e => {
log(true, 'TASK', `Mobile flow failed early for ${account.email}: ${e instanceof Error ? e.message : e}`,'error')
errors.push(formatFullErr('mobile', e)); return null
})
const [desktopResult, mobileResult] = await Promise.all([desktopPromise, mobilePromise])
if (desktopResult) {
desktopInitial = desktopResult.initialPoints
desktopCollected = desktopResult.collectedPoints
@@ -188,14 +202,20 @@ export class MicrosoftRewardsBot {
}
} else {
this.isMobile = false
const desktopResult = await this.Desktop(account).catch(e => { errors.push(`desktop:${shortErr(e)}`); return null })
const desktopResult = await this.Desktop(account).catch(e => {
log(false, 'TASK', `Desktop flow failed early for ${account.email}: ${e instanceof Error ? e.message : e}`,'error')
errors.push(formatFullErr('desktop', e)); return null
})
if (desktopResult) {
desktopInitial = desktopResult.initialPoints
desktopCollected = desktopResult.collectedPoints
}
this.isMobile = true
const mobileResult = await this.Mobile(account).catch(e => { errors.push(`mobile:${shortErr(e)}`); return null })
const mobileResult = await this.Mobile(account).catch(e => {
log(true, 'TASK', `Mobile flow failed early for ${account.email}: ${e instanceof Error ? e.message : e}`,'error')
errors.push(formatFullErr('mobile', e)); return null
})
if (mobileResult) {
mobileInitial = mobileResult.initialPoints
mobileCollected = mobileResult.collectedPoints
@@ -221,6 +241,12 @@ export class MicrosoftRewardsBot {
}
log(this.isMobile, 'MAIN-PRIMARY', 'Completed tasks for ALL accounts', 'log', 'green')
// Extra diagnostic summary when verbose
if (process.env.DEBUG_REWARDS_VERBOSE === '1') {
for (const summary of this.accountSummaries) {
log('main','SUMMARY-DEBUG',`Account ${summary.email} collected D:${summary.desktopCollected} M:${summary.mobileCollected} TOTAL:${summary.totalCollected} ERRORS:${summary.errors.length ? summary.errors.join(';') : 'none'}`)
}
}
// If in worker mode (clusters>1) send summaries to primary
if (this.config.clusters > 1 && !cluster.isPrimary) {
if (process.send) {
@@ -235,6 +261,7 @@ export class MicrosoftRewardsBot {
// Desktop
async Desktop(account: Account) {
log(false,'FLOW','Desktop() invoked')
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
this.homePage = await browser.newPage()
@@ -311,6 +338,7 @@ export class MicrosoftRewardsBot {
// Mobile
async Mobile(account: Account) {
log(true,'FLOW','Mobile() invoked')
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
this.homePage = await browser.newPage()

View File

@@ -1,78 +0,0 @@
// Fallback in case @types/node not installed yet; ensures Process/stubs to reduce red squiggles.
// Prefer installing @types/node for full types.
interface ProcessEnv { [key: string]: string | undefined }
interface Process {
pid: number
exit(code?: number): never
send?(message: any): void
on(event: string, listener: (...args: any[]) => void): any
stdin: { on(event: string, listener: (...args: any[]) => void): any }
stdout: { write(chunk: any): boolean }
env: ProcessEnv
}
declare var process: Process
// Minimal axios module declaration
declare module 'axios' {
export interface AxiosRequestConfig { [key: string]: any }
export interface AxiosResponse<T = any> { data: T }
export interface AxiosInstance {
defaults: any
request<T=any>(config: AxiosRequestConfig): Promise<AxiosResponse<T>>
}
export interface AxiosStatic {
(config: AxiosRequestConfig): Promise<AxiosResponse>
request<T=any>(config: AxiosRequestConfig): Promise<AxiosResponse<T>>
create(config?: AxiosRequestConfig): AxiosInstance
}
const axios: AxiosStatic
export default axios
}
// Minimal readline
declare module 'readline' {
export interface Interface { question(query: string, cb: (answer: string)=>void): void; close(): void }
export function createInterface(opts: any): Interface
export default {} as any
}
// Minimal crypto
declare module 'crypto' {
export function randomBytes(size: number): { toString(encoding: string): string }
}
// Minimal os module
declare module 'os' {
export function platform(): string
}
// Minimal cheerio subset
declare module 'cheerio' {
export interface CheerioAPI {
(selector: any): any
load(html: string): CheerioAPI
text(): string
}
export function load(html: string): CheerioAPI
}
declare module 'cluster' {
import { EventEmitter } from 'events'
interface WorkerLike extends EventEmitter {
id: number
process: { pid: number }
send?(message: any): void
on(event: 'message', listener: (msg: any) => void): any
}
interface Cluster extends EventEmitter {
isPrimary: boolean
fork(env?: NodeJS.ProcessEnv): WorkerLike
workers?: Record<string, WorkerLike>
on(event: 'exit', listener: (worker: WorkerLike, code: number) => void): any
}
const cluster: Cluster
export default cluster
}

View File

@@ -1,57 +0,0 @@
// Minimal module declaration to silence TS complaints if upstream types not found.
// You should replace with actual types if the package provides them.
// Basic playwright stubs (only what we currently need). Replace with real @types if available.
declare module 'playwright' {
export interface Cookie { name: string; value: string; domain?: string; path?: string; expires?: number; httpOnly?: boolean; secure?: boolean; sameSite?: 'Lax'|'Strict'|'None' }
export interface BrowserContext {
newPage(): Promise<Page>
setDefaultTimeout(timeout: number): void
addCookies(cookies: Cookie[]): Promise<void>
cookies(): Promise<Cookie[]>
pages(): Page[]
close(): Promise<void>
}
export interface Browser {
newPage(): Promise<Page>
context(): BrowserContext
close(): Promise<void>
pages?(): Page[]
}
export interface Keyboard {
type(text: string): Promise<any>
press(key: string): Promise<any>
down(key: string): Promise<any>
up(key: string): Promise<any>
}
export interface Locator {
first(): Locator
click(opts?: any): Promise<any>
isVisible(opts?: any): Promise<boolean>
nth(index: number): Locator
}
export interface Page {
goto(url: string, opts?: any): Promise<any>
waitForLoadState(state?: string, opts?: any): Promise<any>
waitForSelector(selector: string, opts?: any): Promise<any>
fill(selector: string, value: string): Promise<any>
keyboard: Keyboard
click(selector: string, opts?: any): Promise<any>
close(): Promise<any>
url(): string
route(match: string, handler: any): Promise<any>
locator(selector: string): Locator
$: (selector: string) => Promise<any>
context(): BrowserContext
reload(opts?: any): Promise<any>
evaluate<R=any>(pageFunction: any, arg?: any): Promise<R>
content(): Promise<string>
waitForTimeout(timeout: number): Promise<void>
}
export interface ChromiumType { launch(opts?: any): Promise<Browser> }
export const chromium: ChromiumType
}
declare module 'rebrowser-playwright' {
export * from 'playwright'
}

View File

@@ -1,25 +0,0 @@
// Minimal shims to silence TypeScript errors in environments without @types/node
// If possible, install @types/node instead for full typing.
declare const __dirname: string
declare namespace NodeJS { interface Process { pid: number; send?: (msg: any) => void; exit(code?: number): void; } }
declare const process: NodeJS.Process
declare module 'cluster' {
interface Worker { process: { pid: number }; on(event: 'message', cb: (msg: any) => void): void }
const isPrimary: boolean
function fork(): Worker
function on(event: 'exit', cb: (worker: Worker, code: number) => void): void
export { isPrimary, fork, on, Worker }
export default { isPrimary, fork, on }
}
declare module 'fs' { const x: any; export = x }
declare module 'path' { const x: any; export = x }
// Do NOT redeclare 'Page' to avoid erasing actual Playwright types if present.
// If types are missing, install: npm i -D @types/node

View File

@@ -39,9 +39,10 @@
"noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "types": ["node"], // Removed explicit requirement; using local shims. Install @types/node for full typings.
"typeRoots": ["./src/types", "./node_modules/@types"],
"moduleResolution":"node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"types": ["node"],
"typeRoots": ["./node_modules/@types"],
// Keep explicit typeRoots to ensure resolution in environments that don't auto-detect before full install.
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */