mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-10 17:26:17 +00:00
feat: Centralize timeout constants and improve navigation error handling in Login flow
This commit is contained in:
@@ -1,99 +0,0 @@
|
|||||||
# 🎯 Account Creator - How to Use
|
|
||||||
|
|
||||||
## ⚠️ CRITICAL: The `--` separator
|
|
||||||
|
|
||||||
**npm consumes `-y` if you don't use `--` !**
|
|
||||||
|
|
||||||
### ❌ WRONG (doesn't work)
|
|
||||||
```powershell
|
|
||||||
npm run creator "URL" -y
|
|
||||||
# Result: -y is consumed by npm, not passed to the script
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ CORRECT (works)
|
|
||||||
```powershell
|
|
||||||
npm run creator -- "URL" -y
|
|
||||||
# The -- tells npm: "everything after belongs to the script"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Examples
|
|
||||||
|
|
||||||
### Auto mode (no questions asked)
|
|
||||||
```powershell
|
|
||||||
npm run creator -- "https://rewards.bing.com/welcome?rh=E3DCB441" -y
|
|
||||||
```
|
|
||||||
|
|
||||||
### Auto mode with recovery email
|
|
||||||
```powershell
|
|
||||||
npm run creator -- "https://rewards.bing.com/welcome?rh=E3DCB441" -y maxou.freq@gmail.com
|
|
||||||
```
|
|
||||||
|
|
||||||
### Interactive mode (asks for options)
|
|
||||||
```powershell
|
|
||||||
npm run creator -- "https://rewards.bing.com/welcome?rh=E3DCB441"
|
|
||||||
```
|
|
||||||
|
|
||||||
### -y can be BEFORE the URL too
|
|
||||||
```powershell
|
|
||||||
npm run creator -- -y "https://rewards.bing.com/welcome?rh=E3DCB441"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 Flags
|
|
||||||
|
|
||||||
| Flag | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| `--` | **REQUIRED** - Separates npm args from script args |
|
|
||||||
| `-y` or `--yes` | Auto-accept all prompts (no questions) |
|
|
||||||
| `"URL"` | Referral URL (use quotes!) |
|
|
||||||
| `email@domain.com` | Recovery email (optional) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚨 Common Errors
|
|
||||||
|
|
||||||
### "Generate email automatically? (Y/n):" appears even with -y
|
|
||||||
|
|
||||||
**Cause**: You forgot the `--` separator
|
|
||||||
|
|
||||||
**Fix**: Add `--` after `npm run creator`
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
# ❌ WRONG
|
|
||||||
npm run creator "URL" -y
|
|
||||||
|
|
||||||
# ✅ CORRECT
|
|
||||||
npm run creator -- "URL" -y
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### URL is truncated at & character
|
|
||||||
|
|
||||||
**Cause**: URL not wrapped in quotes
|
|
||||||
|
|
||||||
**Fix**: Always use quotes around URLs
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
# ❌ WRONG
|
|
||||||
npm run creator -- https://rewards.bing.com/welcome?rh=CODE&ref=xxx -y
|
|
||||||
|
|
||||||
# ✅ CORRECT
|
|
||||||
npm run creator -- "https://rewards.bing.com/welcome?rh=CODE&ref=xxx" -y
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 Full Command Template
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
npm run creator -- "https://rewards.bing.com/welcome?rh=YOUR_CODE" -y your.email@gmail.com
|
|
||||||
↑ ↑ ↑ ↑
|
|
||||||
| | | |
|
|
||||||
| URL in quotes (required if contains &) | Optional recovery email
|
|
||||||
| |
|
|
||||||
-- separator (REQUIRED for -y to work) -y flag (auto mode)
|
|
||||||
```
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
# 🐛 Automatic Error Reporting
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The bot automatically reports errors to a community webhook to help identify and fix issues faster, without requiring manual bug reports from users.
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
|
|
||||||
✅ **Privacy-First** - Only non-sensitive error information is sent
|
|
||||||
✅ **Opt-Out** - Can be disabled in configuration
|
|
||||||
✅ **Obfuscated Webhook** - URL is base64-encoded
|
|
||||||
✅ **Automatic Sanitization** - Removes emails, paths, tokens, and IPs
|
|
||||||
✅ **Intelligent Filtering** - Excludes user configuration errors and false positives
|
|
||||||
✅ **System Information** - Includes version, platform, architecture for debugging
|
|
||||||
|
|
||||||
## What's Sent
|
|
||||||
|
|
||||||
- Error message (sanitized)
|
|
||||||
- Stack trace (truncated, sanitized)
|
|
||||||
- Bot version
|
|
||||||
- Operating system and architecture
|
|
||||||
- Node.js version
|
|
||||||
- Timestamp
|
|
||||||
|
|
||||||
## What's NOT Sent
|
|
||||||
|
|
||||||
- ❌ Email addresses (redacted)
|
|
||||||
- ❌ File paths (redacted)
|
|
||||||
- ❌ IP addresses (redacted)
|
|
||||||
- ❌ Tokens/API keys (redacted)
|
|
||||||
- ❌ Account credentials
|
|
||||||
- ❌ Personal information
|
|
||||||
|
|
||||||
## Filtered Errors
|
|
||||||
|
|
||||||
The system intelligently filters out:
|
|
||||||
- User configuration errors (missing files, invalid credentials)
|
|
||||||
- Expected errors (no points, already completed)
|
|
||||||
- Network issues (proxy failures, port conflicts)
|
|
||||||
- Account-specific issues (banned accounts)
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
In `config.jsonc`:
|
|
||||||
|
|
||||||
```jsonc
|
|
||||||
"errorReporting": {
|
|
||||||
"enabled": true, // Set to false to disable
|
|
||||||
"webhookUrl": "aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTQzNzExMTk2MjM5NDY4OTYyOS90bHZHS1phSDktckppcjR0blpLU1pwUkhTM1liZU40dlpudUN2NTBrNU1wQURZUlBuSG5aNk15YkFsZ0Y1UUZvNktIXw=="
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Disable Error Reporting
|
|
||||||
|
|
||||||
Set `enabled` to `false`:
|
|
||||||
|
|
||||||
```jsonc
|
|
||||||
"errorReporting": {
|
|
||||||
"enabled": false,
|
|
||||||
"webhookUrl": "..."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Privacy & Security
|
|
||||||
|
|
||||||
- No PII is sent
|
|
||||||
- All sensitive data is automatically redacted
|
|
||||||
- Webhook URL is obfuscated (base64)
|
|
||||||
- Fire-and-forget (never blocks execution)
|
|
||||||
- Silent failure if webhook is unreachable
|
|
||||||
|
|
||||||
## Technical Details
|
|
||||||
|
|
||||||
- **File**: `src/util/ErrorReportingWebhook.ts`
|
|
||||||
- **Integration**: Automatic via `Logger.ts`
|
|
||||||
- **Method**: HTTP POST to Discord webhook
|
|
||||||
- **Timeout**: 10 seconds
|
|
||||||
- **Filtering**: Pattern-based false positive detection
|
|
||||||
|
|
||||||
Thank you for helping improve the bot! 🙏
|
|
||||||
@@ -3,7 +3,6 @@ import * as crypto from 'crypto'
|
|||||||
import type { Locator, Page } from 'playwright'
|
import type { Locator, Page } from 'playwright'
|
||||||
import readline from 'readline'
|
import readline from 'readline'
|
||||||
|
|
||||||
import { TIMEOUTS } from '../constants'
|
|
||||||
import { MicrosoftRewardsBot } from '../index'
|
import { MicrosoftRewardsBot } from '../index'
|
||||||
import { OAuth } from '../interface/OAuth'
|
import { OAuth } from '../interface/OAuth'
|
||||||
import { saveSessionData } from '../util/Load'
|
import { saveSessionData } from '../util/Load'
|
||||||
@@ -40,19 +39,32 @@ const SELECTORS = {
|
|||||||
|
|
||||||
const LOGIN_TARGET = { host: 'rewards.bing.com', path: '/' }
|
const LOGIN_TARGET = { host: 'rewards.bing.com', path: '/' }
|
||||||
|
|
||||||
|
// Centralized timeouts to replace magic numbers throughout the file
|
||||||
const DEFAULT_TIMEOUTS = {
|
const DEFAULT_TIMEOUTS = {
|
||||||
loginMaxMs: (() => {
|
loginMaxMs: (() => {
|
||||||
const val = Number(process.env.LOGIN_MAX_WAIT_MS || 180000)
|
const val = Number(process.env.LOGIN_MAX_WAIT_MS || 180000)
|
||||||
// IMPROVED: Use isFinite instead of isNaN for consistency
|
|
||||||
return (!Number.isFinite(val) || val < 10000 || val > 600000) ? 180000 : val
|
return (!Number.isFinite(val) || val < 10000 || val > 600000) ? 180000 : val
|
||||||
})(),
|
})(),
|
||||||
short: 200,
|
short: 200,
|
||||||
medium: 800,
|
medium: 800,
|
||||||
long: 1500,
|
long: 1500,
|
||||||
|
veryLong: 2000,
|
||||||
|
extraLong: 3000,
|
||||||
oauthMaxMs: 180000,
|
oauthMaxMs: 180000,
|
||||||
portalWaitMs: 15000,
|
portalWaitMs: 15000,
|
||||||
elementCheck: 100,
|
elementCheck: 100,
|
||||||
fastPoll: 500
|
fastPoll: 500,
|
||||||
|
emailFieldWait: 8000,
|
||||||
|
passwordFieldWait: 4000,
|
||||||
|
rewardsPortalCheck: 8000,
|
||||||
|
navigationTimeout: 30000,
|
||||||
|
navigationTimeoutLinux: 60000,
|
||||||
|
totpThrottle: 5000,
|
||||||
|
totpWait: 1200,
|
||||||
|
passkeyNoPromptLog: 10000,
|
||||||
|
twoFactorTimeout: 120000,
|
||||||
|
bingVerificationMaxIterations: 10,
|
||||||
|
bingVerificationMaxIterationsMobile: 8
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
// Security pattern bundle
|
// Security pattern bundle
|
||||||
@@ -93,6 +105,72 @@ export class Login {
|
|||||||
this.cleanupCompromisedInterval()
|
this.cleanupCompromisedInterval()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reusable navigation with retry logic and chrome-error recovery
|
||||||
|
* Eliminates duplicate navigation code throughout the file
|
||||||
|
*/
|
||||||
|
private async navigateWithRetry(
|
||||||
|
page: Page,
|
||||||
|
url: string,
|
||||||
|
context: string,
|
||||||
|
maxAttempts = 3
|
||||||
|
): Promise<{ success: boolean; recoveryUsed: boolean }> {
|
||||||
|
const isLinux = process.platform === 'linux'
|
||||||
|
const navigationTimeout = isLinux ? DEFAULT_TIMEOUTS.navigationTimeoutLinux : DEFAULT_TIMEOUTS.navigationTimeout
|
||||||
|
|
||||||
|
let navigationSucceeded = false
|
||||||
|
let recoveryUsed = false
|
||||||
|
let attempts = 0
|
||||||
|
|
||||||
|
while (!navigationSucceeded && attempts < maxAttempts) {
|
||||||
|
attempts++
|
||||||
|
try {
|
||||||
|
await page.goto(url, {
|
||||||
|
waitUntil: 'domcontentloaded',
|
||||||
|
timeout: navigationTimeout
|
||||||
|
})
|
||||||
|
navigationSucceeded = true
|
||||||
|
} catch (error) {
|
||||||
|
const errorMsg = error instanceof Error ? error.message : String(error)
|
||||||
|
|
||||||
|
// Chrome-error recovery pattern
|
||||||
|
if (errorMsg.includes('chrome-error://chromewebdata/')) {
|
||||||
|
this.bot.log(this.bot.isMobile, context, `Navigation interrupted by chrome-error (attempt ${attempts}/${maxAttempts}), attempting recovery...`, 'warn')
|
||||||
|
|
||||||
|
await this.bot.utils.wait(DEFAULT_TIMEOUTS.long)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await page.reload({ waitUntil: 'domcontentloaded', timeout: navigationTimeout })
|
||||||
|
navigationSucceeded = true
|
||||||
|
recoveryUsed = true
|
||||||
|
this.bot.log(this.bot.isMobile, context, '✓ Recovery successful via reload')
|
||||||
|
} catch (reloadError) {
|
||||||
|
if (attempts < maxAttempts) {
|
||||||
|
this.bot.log(this.bot.isMobile, context, `Reload failed (attempt ${attempts}/${maxAttempts}), trying fresh navigation...`, 'warn')
|
||||||
|
await this.bot.utils.wait(DEFAULT_TIMEOUTS.veryLong)
|
||||||
|
} else {
|
||||||
|
throw reloadError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (errorMsg.includes('ERR_PROXY_CONNECTION_FAILED') || errorMsg.includes('ERR_TUNNEL_CONNECTION_FAILED')) {
|
||||||
|
this.bot.log(this.bot.isMobile, context, `Proxy connection failed (attempt ${attempts}/${maxAttempts}): ${errorMsg}`, 'warn')
|
||||||
|
if (attempts < maxAttempts) {
|
||||||
|
await this.bot.utils.wait(DEFAULT_TIMEOUTS.extraLong * attempts)
|
||||||
|
} else {
|
||||||
|
throw new Error(`Proxy connection failed for ${context} - check proxy configuration`)
|
||||||
|
}
|
||||||
|
} else if (attempts < maxAttempts) {
|
||||||
|
this.bot.log(this.bot.isMobile, context, `Navigation failed (attempt ${attempts}/${maxAttempts}): ${errorMsg}`, 'warn')
|
||||||
|
await this.bot.utils.wait(DEFAULT_TIMEOUTS.veryLong * attempts)
|
||||||
|
} else {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: navigationSucceeded, recoveryUsed }
|
||||||
|
}
|
||||||
|
|
||||||
// --------------- Public API ---------------
|
// --------------- Public API ---------------
|
||||||
async login(page: Page, email: string, password: string, totpSecret?: string) {
|
async login(page: Page, email: string, password: string, totpSecret?: string) {
|
||||||
try {
|
try {
|
||||||
@@ -115,58 +193,12 @@ export class Login {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLinux = process.platform === 'linux'
|
// IMPROVEMENT: Use centralized navigation retry logic
|
||||||
const navigationTimeout = isLinux ? 60000 : 30000
|
const { success: navigationSucceeded, recoveryUsed } = await this.navigateWithRetry(
|
||||||
|
page,
|
||||||
// IMPROVEMENT: Try initial navigation with better error handling
|
'https://www.bing.com/rewards/dashboard',
|
||||||
let navigationSucceeded = false
|
'LOGIN'
|
||||||
let recoveryUsed = false
|
)
|
||||||
let attempts = 0
|
|
||||||
const maxAttempts = 3
|
|
||||||
|
|
||||||
while (!navigationSucceeded && attempts < maxAttempts) {
|
|
||||||
attempts++
|
|
||||||
try {
|
|
||||||
await page.goto('https://www.bing.com/rewards/dashboard', {
|
|
||||||
waitUntil: 'domcontentloaded',
|
|
||||||
timeout: navigationTimeout
|
|
||||||
})
|
|
||||||
navigationSucceeded = true
|
|
||||||
} catch (error) {
|
|
||||||
const errorMsg = error instanceof Error ? error.message : String(error)
|
|
||||||
|
|
||||||
// If interrupted by chrome-error, retry with reload approach
|
|
||||||
if (errorMsg.includes('chrome-error://chromewebdata/')) {
|
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN', `Navigation interrupted by chrome-error (attempt ${attempts}/${maxAttempts}), attempting recovery...`, 'warn')
|
|
||||||
|
|
||||||
// Wait a bit for page to settle
|
|
||||||
await this.bot.utils.wait(1500) // Increased from 1000ms
|
|
||||||
|
|
||||||
// Try reload which usually fixes the issue
|
|
||||||
try {
|
|
||||||
await page.reload({ waitUntil: 'domcontentloaded', timeout: navigationTimeout })
|
|
||||||
navigationSucceeded = true
|
|
||||||
recoveryUsed = true
|
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN', '✓ Recovery successful via reload')
|
|
||||||
} catch (reloadError) {
|
|
||||||
// Last resort: try goto again
|
|
||||||
if (attempts < maxAttempts) {
|
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN', `Reload failed (attempt ${attempts}/${maxAttempts}), trying fresh navigation...`, 'warn')
|
|
||||||
await this.bot.utils.wait(2000) // Increased from 1500ms
|
|
||||||
} else {
|
|
||||||
throw reloadError // Exhausted attempts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (attempts < maxAttempts) {
|
|
||||||
// Different error, retry with backoff
|
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN', `Navigation failed (attempt ${attempts}/${maxAttempts}): ${errorMsg}`, 'warn')
|
|
||||||
await this.bot.utils.wait(2000 * attempts) // Exponential backoff
|
|
||||||
} else {
|
|
||||||
// Exhausted attempts, rethrow
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!navigationSucceeded) {
|
if (!navigationSucceeded) {
|
||||||
throw new Error('Failed to navigate to dashboard after multiple attempts')
|
throw new Error('Failed to navigate to dashboard after multiple attempts')
|
||||||
@@ -174,7 +206,7 @@ export class Login {
|
|||||||
|
|
||||||
// Only check for HTTP 400 if recovery was NOT used (to avoid double reload)
|
// Only check for HTTP 400 if recovery was NOT used (to avoid double reload)
|
||||||
if (!recoveryUsed) {
|
if (!recoveryUsed) {
|
||||||
await this.bot.utils.wait(500)
|
await this.bot.utils.wait(DEFAULT_TIMEOUTS.fastPoll)
|
||||||
const content = await page.content().catch(() => '')
|
const content = await page.content().catch(() => '')
|
||||||
const hasHttp400 = content.includes('HTTP ERROR 400') ||
|
const hasHttp400 = content.includes('HTTP ERROR 400') ||
|
||||||
content.includes('This page isn\'t working') ||
|
content.includes('This page isn\'t working') ||
|
||||||
@@ -182,8 +214,10 @@ export class Login {
|
|||||||
|
|
||||||
if (hasHttp400) {
|
if (hasHttp400) {
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'HTTP 400 detected in content, reloading...', 'warn')
|
this.bot.log(this.bot.isMobile, 'LOGIN', 'HTTP 400 detected in content, reloading...', 'warn')
|
||||||
await page.reload({ waitUntil: 'domcontentloaded', timeout: navigationTimeout })
|
const isLinux = process.platform === 'linux'
|
||||||
await this.bot.utils.wait(1000)
|
const timeout = isLinux ? DEFAULT_TIMEOUTS.navigationTimeoutLinux : DEFAULT_TIMEOUTS.navigationTimeout
|
||||||
|
await page.reload({ waitUntil: 'domcontentloaded', timeout })
|
||||||
|
await this.bot.utils.wait(DEFAULT_TIMEOUTS.medium)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,61 +281,19 @@ export class Login {
|
|||||||
url.searchParams.set('access_type', 'offline_access')
|
url.searchParams.set('access_type', 'offline_access')
|
||||||
url.searchParams.set('login_hint', email)
|
url.searchParams.set('login_hint', email)
|
||||||
|
|
||||||
const isLinux = process.platform === 'linux'
|
// Use centralized navigation retry logic
|
||||||
const navigationTimeout = isLinux ? 60000 : 30000
|
const { success: navigationSucceeded, recoveryUsed } = await this.navigateWithRetry(
|
||||||
|
page,
|
||||||
let navigationSucceeded = false
|
url.href,
|
||||||
let recoveryUsed = false
|
'LOGIN-APP'
|
||||||
let attempts = 0
|
)
|
||||||
const maxAttempts = 3
|
|
||||||
|
|
||||||
while (!navigationSucceeded && attempts < maxAttempts) {
|
|
||||||
attempts++
|
|
||||||
try {
|
|
||||||
await page.goto(url.href, { waitUntil: 'domcontentloaded', timeout: navigationTimeout })
|
|
||||||
navigationSucceeded = true
|
|
||||||
} catch (error) {
|
|
||||||
const errorMsg = error instanceof Error ? error.message : String(error)
|
|
||||||
|
|
||||||
if (errorMsg.includes('chrome-error://chromewebdata/')) {
|
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN-APP', `OAuth navigation interrupted by chrome-error (attempt ${attempts}/${maxAttempts}), recovering...`, 'warn')
|
|
||||||
await this.bot.utils.wait(1500)
|
|
||||||
|
|
||||||
try {
|
|
||||||
await page.reload({ waitUntil: 'domcontentloaded', timeout: navigationTimeout })
|
|
||||||
navigationSucceeded = true
|
|
||||||
recoveryUsed = true
|
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN-APP', 'OAuth recovery successful')
|
|
||||||
} catch (reloadError) {
|
|
||||||
if (attempts < maxAttempts) {
|
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN-APP', `Reload failed (attempt ${attempts}/${maxAttempts}), retrying...`, 'warn')
|
|
||||||
await this.bot.utils.wait(2000)
|
|
||||||
} else {
|
|
||||||
throw reloadError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (errorMsg.includes('ERR_PROXY_CONNECTION_FAILED') || errorMsg.includes('ERR_TUNNEL_CONNECTION_FAILED')) {
|
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN-APP', `Proxy connection failed (attempt ${attempts}/${maxAttempts}): ${errorMsg}`, 'warn')
|
|
||||||
if (attempts < maxAttempts) {
|
|
||||||
await this.bot.utils.wait(3000 * attempts)
|
|
||||||
} else {
|
|
||||||
throw new Error('Proxy connection failed for OAuth - check proxy configuration')
|
|
||||||
}
|
|
||||||
} else if (attempts < maxAttempts) {
|
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN-APP', `Navigation failed (attempt ${attempts}/${maxAttempts}): ${errorMsg}`, 'warn')
|
|
||||||
await this.bot.utils.wait(2000 * attempts)
|
|
||||||
} else {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!navigationSucceeded) {
|
if (!navigationSucceeded) {
|
||||||
throw new Error('Failed to navigate to OAuth page after multiple attempts')
|
throw new Error('Failed to navigate to OAuth page after multiple attempts')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!recoveryUsed) {
|
if (!recoveryUsed) {
|
||||||
await this.bot.utils.wait(500)
|
await this.bot.utils.wait(DEFAULT_TIMEOUTS.fastPoll)
|
||||||
const content = await page.content().catch((err) => {
|
const content = await page.content().catch((err) => {
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN-APP', `Failed to get page content for HTTP 400 check: ${err}`, 'warn')
|
this.bot.log(this.bot.isMobile, 'LOGIN-APP', `Failed to get page content for HTTP 400 check: ${err}`, 'warn')
|
||||||
return ''
|
return ''
|
||||||
@@ -312,8 +304,10 @@ export class Login {
|
|||||||
|
|
||||||
if (hasHttp400) {
|
if (hasHttp400) {
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN-APP', 'HTTP 400 detected, reloading...', 'warn')
|
this.bot.log(this.bot.isMobile, 'LOGIN-APP', 'HTTP 400 detected, reloading...', 'warn')
|
||||||
await page.reload({ waitUntil: 'domcontentloaded', timeout: navigationTimeout })
|
const isLinux = process.platform === 'linux'
|
||||||
await this.bot.utils.wait(1000)
|
const timeout = isLinux ? DEFAULT_TIMEOUTS.navigationTimeoutLinux : DEFAULT_TIMEOUTS.navigationTimeout
|
||||||
|
await page.reload({ waitUntil: 'domcontentloaded', timeout })
|
||||||
|
await this.bot.utils.wait(DEFAULT_TIMEOUTS.medium)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
@@ -410,35 +404,12 @@ export class Login {
|
|||||||
private async tryReuseExistingSession(page: Page): Promise<boolean> {
|
private async tryReuseExistingSession(page: Page): Promise<boolean> {
|
||||||
const homeUrl = 'https://rewards.bing.com/'
|
const homeUrl = 'https://rewards.bing.com/'
|
||||||
try {
|
try {
|
||||||
const isLinux = process.platform === 'linux'
|
// Use centralized navigation retry logic
|
||||||
const navigationTimeout = isLinux ? 60000 : 30000
|
const { success: navigationSucceeded, recoveryUsed } = await this.navigateWithRetry(
|
||||||
|
page,
|
||||||
// Try navigation with error recovery
|
homeUrl,
|
||||||
let navigationSucceeded = false
|
'LOGIN'
|
||||||
let recoveryUsed = false
|
)
|
||||||
try {
|
|
||||||
await page.goto(homeUrl, { timeout: navigationTimeout })
|
|
||||||
navigationSucceeded = true
|
|
||||||
} catch (error) {
|
|
||||||
const errorMsg = error instanceof Error ? error.message : String(error)
|
|
||||||
|
|
||||||
if (errorMsg.includes('chrome-error://chromewebdata/')) {
|
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Session check interrupted, recovering...', 'warn')
|
|
||||||
await this.bot.utils.wait(1000)
|
|
||||||
|
|
||||||
try {
|
|
||||||
await page.reload({ waitUntil: 'domcontentloaded', timeout: navigationTimeout })
|
|
||||||
navigationSucceeded = true
|
|
||||||
recoveryUsed = true
|
|
||||||
} catch (reloadError) {
|
|
||||||
await this.bot.utils.wait(1500)
|
|
||||||
await page.goto(homeUrl, { timeout: navigationTimeout })
|
|
||||||
navigationSucceeded = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!navigationSucceeded) return false
|
if (!navigationSucceeded) return false
|
||||||
|
|
||||||
@@ -446,7 +417,7 @@ export class Login {
|
|||||||
|
|
||||||
// Only check HTTP 400 if recovery was NOT used
|
// Only check HTTP 400 if recovery was NOT used
|
||||||
if (!recoveryUsed) {
|
if (!recoveryUsed) {
|
||||||
await this.bot.utils.wait(500)
|
await this.bot.utils.wait(DEFAULT_TIMEOUTS.fastPoll)
|
||||||
const content = await page.content().catch(() => '')
|
const content = await page.content().catch(() => '')
|
||||||
const hasHttp400 = content.includes('HTTP ERROR 400') ||
|
const hasHttp400 = content.includes('HTTP ERROR 400') ||
|
||||||
content.includes('This page isn\'t working') ||
|
content.includes('This page isn\'t working') ||
|
||||||
@@ -454,8 +425,10 @@ export class Login {
|
|||||||
|
|
||||||
if (hasHttp400) {
|
if (hasHttp400) {
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'HTTP 400 on session check, reloading...', 'warn')
|
this.bot.log(this.bot.isMobile, 'LOGIN', 'HTTP 400 on session check, reloading...', 'warn')
|
||||||
await page.reload({ waitUntil: 'domcontentloaded', timeout: navigationTimeout })
|
const isLinux = process.platform === 'linux'
|
||||||
await this.bot.utils.wait(1000)
|
const timeout = isLinux ? DEFAULT_TIMEOUTS.navigationTimeoutLinux : DEFAULT_TIMEOUTS.navigationTimeout
|
||||||
|
await page.reload({ waitUntil: 'domcontentloaded', timeout })
|
||||||
|
await this.bot.utils.wait(DEFAULT_TIMEOUTS.medium)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this.bot.browser.utils.reloadBadPage(page)
|
await this.bot.browser.utils.reloadBadPage(page)
|
||||||
@@ -1244,63 +1217,21 @@ export class Login {
|
|||||||
try {
|
try {
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN-BING', 'Verifying Bing auth context')
|
this.bot.log(this.bot.isMobile, 'LOGIN-BING', 'Verifying Bing auth context')
|
||||||
|
|
||||||
const isLinux = process.platform === 'linux'
|
|
||||||
const navigationTimeout = isLinux ? 60000 : 30000
|
|
||||||
const verificationUrl = 'https://www.bing.com/fd/auth/signin?action=interactive&provider=windows_live_id&return_url=https%3A%2F%2Fwww.bing.com%2F'
|
const verificationUrl = 'https://www.bing.com/fd/auth/signin?action=interactive&provider=windows_live_id&return_url=https%3A%2F%2Fwww.bing.com%2F'
|
||||||
|
|
||||||
let navigationSucceeded = false
|
// Use centralized navigation retry logic
|
||||||
let attempts = 0
|
const { success: navigationSucceeded } = await this.navigateWithRetry(
|
||||||
const maxAttempts = 3
|
page,
|
||||||
|
verificationUrl,
|
||||||
while (!navigationSucceeded && attempts < maxAttempts) {
|
'LOGIN-BING'
|
||||||
attempts++
|
)
|
||||||
try {
|
|
||||||
await page.goto(verificationUrl, {
|
|
||||||
waitUntil: 'domcontentloaded',
|
|
||||||
timeout: navigationTimeout
|
|
||||||
})
|
|
||||||
navigationSucceeded = true
|
|
||||||
} catch (error) {
|
|
||||||
const errorMsg = error instanceof Error ? error.message : String(error)
|
|
||||||
|
|
||||||
if (errorMsg.includes('chrome-error://chromewebdata/')) {
|
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN-BING', `Bing verification interrupted by chrome-error (attempt ${attempts}/${maxAttempts}), recovering...`, 'warn')
|
|
||||||
await this.bot.utils.wait(1500)
|
|
||||||
|
|
||||||
try {
|
|
||||||
await page.reload({ waitUntil: 'domcontentloaded', timeout: navigationTimeout })
|
|
||||||
navigationSucceeded = true
|
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN-BING', 'Bing verification recovery successful')
|
|
||||||
} catch (reloadError) {
|
|
||||||
if (attempts < maxAttempts) {
|
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN-BING', `Reload failed (attempt ${attempts}/${maxAttempts}), retrying navigation...`, 'warn')
|
|
||||||
await this.bot.utils.wait(2000)
|
|
||||||
} else {
|
|
||||||
throw reloadError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (errorMsg.includes('ERR_PROXY_CONNECTION_FAILED') || errorMsg.includes('ERR_TUNNEL_CONNECTION_FAILED')) {
|
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN-BING', `Proxy connection failed (attempt ${attempts}/${maxAttempts}): ${errorMsg}`, 'warn')
|
|
||||||
if (attempts < maxAttempts) {
|
|
||||||
await this.bot.utils.wait(3000 * attempts)
|
|
||||||
} else {
|
|
||||||
throw new Error('Proxy connection failed for Bing verification - check proxy configuration')
|
|
||||||
}
|
|
||||||
} else if (attempts < maxAttempts) {
|
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN-BING', `Navigation failed (attempt ${attempts}/${maxAttempts}): ${errorMsg}`, 'warn')
|
|
||||||
await this.bot.utils.wait(2000 * attempts)
|
|
||||||
} else {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!navigationSucceeded) {
|
if (!navigationSucceeded) {
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN-BING', 'Bing verification navigation failed after multiple attempts', 'warn')
|
this.bot.log(this.bot.isMobile, 'LOGIN-BING', 'Bing verification navigation failed after multiple attempts', 'warn')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.bot.utils.wait(800)
|
await this.bot.utils.wait(DEFAULT_TIMEOUTS.medium)
|
||||||
const content = await page.content().catch(() => '')
|
const content = await page.content().catch(() => '')
|
||||||
const hasHttp400 = content.includes('HTTP ERROR 400') ||
|
const hasHttp400 = content.includes('HTTP ERROR 400') ||
|
||||||
content.includes('This page isn\'t working') ||
|
content.includes('This page isn\'t working') ||
|
||||||
@@ -1308,11 +1239,13 @@ export class Login {
|
|||||||
|
|
||||||
if (hasHttp400) {
|
if (hasHttp400) {
|
||||||
this.bot.log(this.bot.isMobile, 'LOGIN-BING', 'HTTP 400 detected during Bing verification, reloading...', 'warn')
|
this.bot.log(this.bot.isMobile, 'LOGIN-BING', 'HTTP 400 detected during Bing verification, reloading...', 'warn')
|
||||||
await page.reload({ waitUntil: 'domcontentloaded', timeout: navigationTimeout }).catch(logError('LOGIN-BING', 'Reload after HTTP 400 failed', this.bot.isMobile))
|
const isLinux = process.platform === 'linux'
|
||||||
await this.bot.utils.wait(1000)
|
const timeout = isLinux ? DEFAULT_TIMEOUTS.navigationTimeoutLinux : DEFAULT_TIMEOUTS.navigationTimeout
|
||||||
|
await page.reload({ waitUntil: 'domcontentloaded', timeout }).catch(logError('LOGIN-BING', 'Reload after HTTP 400 failed', this.bot.isMobile))
|
||||||
|
await this.bot.utils.wait(DEFAULT_TIMEOUTS.medium)
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxIterations = this.bot.isMobile ? 8 : 10
|
const maxIterations = this.bot.isMobile ? DEFAULT_TIMEOUTS.bingVerificationMaxIterationsMobile : DEFAULT_TIMEOUTS.bingVerificationMaxIterations
|
||||||
for (let i = 0; i < maxIterations; i++) {
|
for (let i = 0; i < maxIterations; i++) {
|
||||||
const u = new URL(page.url())
|
const u = new URL(page.url())
|
||||||
|
|
||||||
@@ -1700,7 +1633,6 @@ export class Login {
|
|||||||
clearInterval(this.compromisedInterval)
|
clearInterval(this.compromisedInterval)
|
||||||
this.compromisedInterval = undefined
|
this.compromisedInterval = undefined
|
||||||
}
|
}
|
||||||
// IMPROVED: Using centralized constant instead of magic number (5*60*1000)
|
|
||||||
this.compromisedInterval = setInterval(()=>{
|
this.compromisedInterval = setInterval(()=>{
|
||||||
try {
|
try {
|
||||||
this.bot.log(this.bot.isMobile,'SECURITY','Security standby active. Manual review required before proceeding.','warn')
|
this.bot.log(this.bot.isMobile,'SECURITY','Security standby active. Manual review required before proceeding.','warn')
|
||||||
@@ -1708,7 +1640,7 @@ export class Login {
|
|||||||
// Intentionally silent: If logging fails in interval, don't crash the timer
|
// Intentionally silent: If logging fails in interval, don't crash the timer
|
||||||
// The interval will try again in 5 minutes
|
// The interval will try again in 5 minutes
|
||||||
}
|
}
|
||||||
}, TIMEOUTS.FIVE_MINUTES)
|
}, 300000) // 5 minutes = 300000ms
|
||||||
}
|
}
|
||||||
|
|
||||||
private cleanupCompromisedInterval() {
|
private cleanupCompromisedInterval() {
|
||||||
|
|||||||
@@ -308,7 +308,8 @@ export class Search extends Workers {
|
|||||||
|
|
||||||
const trendsData = this.extractJsonFromResponse(rawText)
|
const trendsData = this.extractJsonFromResponse(rawText)
|
||||||
if (!trendsData) {
|
if (!trendsData) {
|
||||||
throw this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', 'Failed to parse Google Trends response', 'error')
|
this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', 'Failed to parse Google Trends response', 'error')
|
||||||
|
throw new Error('Failed to parse Google Trends response')
|
||||||
}
|
}
|
||||||
|
|
||||||
const mappedTrendsData = trendsData.map(query => [query[0], query[9]!.slice(1)])
|
const mappedTrendsData = trendsData.map(query => [query[0], query[9]!.slice(1)])
|
||||||
|
|||||||
Reference in New Issue
Block a user