mirror of
https://github.com/TheNetsky/Microsoft-Rewards-Script.git
synced 2026-01-18 05:53:57 +00:00
V2.1 (#375)
* feat: Implement edge version fetching with retry logic and caching * chore: Update version to 2.1.0 in package.json * fix: Update package version to 2.1.0 and enhance user agent metadata * feat: Enhance 2FA handling with improved TOTP input and submission logic * fix: Refactor getSystemComponents to improve mobile user agent string generation * feat: Add support for cron expressions for advanced scheduling * feat: Improve humanization feature with detailed logging for off-days configuration * feat: Add live log streaming via webhook and enhance logging configuration * fix: Remove unused @types/cron-parser dependency from devDependencies * feat: Add cron-parser dependency and enhance Axios error handling for proxy authentication * feat: Enhance dashboard data retrieval with retry logic and diagnostics capture * feat: Add ready-to-use sample configurations and update configuration settings for better customization * feat: Add buy mode detection and configuration methods for enhanced manual redemption * feat: Migrate configuration from JSON to JSONC format for improved readability and comments support feat: Implement centralized diagnostics capture for better error handling and reporting fix: Update documentation references from config.json to config.jsonc chore: Add .vscode to .gitignore for cleaner project structure refactor: Enhance humanization and diagnostics capture logic in BrowserUtil and Login classes * feat: Reintroduce ambiance declarations for the 'luxon' module to unlock TypeScript * feat: Update search delay settings for improved performance and reliability * feat: Update README and SECURITY documentation for clarity and improved data handling guidelines * Enhance README and SECURITY documentation for Microsoft Rewards Script V2 - Updated README.md to improve structure, add badges, and enhance clarity on features and setup instructions. - Expanded SECURITY.md to provide detailed data handling practices, security guidelines, and best practices for users. - Included sections on data flow, credential management, and responsible use of the automation tool. - Added a security checklist for users to ensure safe practices while using the script. * feat: Réorganiser et enrichir la documentation du README pour une meilleure clarté et accessibilité * feat: Updated and reorganized the README for better presentation and clarity * feat: Revised and simplified the README for better clarity and accessibility * Update README.md
This commit is contained in:
@@ -40,7 +40,14 @@ class Browser {
|
||||
try {
|
||||
// FORCE_HEADLESS env takes precedence (used in Docker with headless shell only)
|
||||
const envForceHeadless = process.env.FORCE_HEADLESS === '1'
|
||||
const headlessValue = envForceHeadless ? true : ((cfgAny['headless'] as boolean | undefined) ?? (cfgAny['browser'] && (cfgAny['browser'] as Record<string, unknown>)['headless'] as boolean | undefined) ?? false)
|
||||
let headlessValue = envForceHeadless ? true : ((cfgAny['headless'] as boolean | undefined) ?? (cfgAny['browser'] && (cfgAny['browser'] as Record<string, unknown>)['headless'] as boolean | undefined) ?? false)
|
||||
if (this.bot.isBuyModeEnabled() && !envForceHeadless) {
|
||||
if (headlessValue !== false) {
|
||||
const target = this.bot.getBuyModeTarget()
|
||||
this.bot.log(this.bot.isMobile, 'BROWSER', `Buy mode detected${target ? ` for ${target}` : ''}; forcing headless=false so captchas and manual flows remain interactive.`, 'warn')
|
||||
}
|
||||
headlessValue = false
|
||||
}
|
||||
const headless: boolean = Boolean(headlessValue)
|
||||
|
||||
const engineName = 'chromium' // current hard-coded engine
|
||||
|
||||
@@ -127,7 +127,7 @@ export default class BrowserFunc {
|
||||
}
|
||||
}
|
||||
|
||||
const scriptContent = await target.evaluate(() => {
|
||||
let scriptContent = await target.evaluate(() => {
|
||||
const scripts = Array.from(document.querySelectorAll('script'))
|
||||
const targetScript = scripts.find(script => script.innerText.includes('var dashboard'))
|
||||
|
||||
@@ -135,7 +135,21 @@ export default class BrowserFunc {
|
||||
})
|
||||
|
||||
if (!scriptContent) {
|
||||
throw this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', 'Dashboard data not found within script', 'error')
|
||||
await this.bot.browser.utils.captureDiagnostics(target, 'dashboard-data-missing').catch(()=>{})
|
||||
// Force a navigation retry once before failing hard
|
||||
try {
|
||||
await this.goHome(target)
|
||||
await target.waitForLoadState('domcontentloaded', { timeout: 5000 }).catch(()=>{})
|
||||
} catch {/* ignore */}
|
||||
const retryContent = await target.evaluate(() => {
|
||||
const scripts = Array.from(document.querySelectorAll('script'))
|
||||
const targetScript = scripts.find(script => script.innerText.includes('var dashboard'))
|
||||
return targetScript?.innerText ? targetScript.innerText : null
|
||||
}).catch(()=>null)
|
||||
if (!retryContent) {
|
||||
throw this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', 'Dashboard data not found within script', 'error')
|
||||
}
|
||||
scriptContent = retryContent
|
||||
}
|
||||
|
||||
// Extract the dashboard object from the script content
|
||||
@@ -151,6 +165,7 @@ export default class BrowserFunc {
|
||||
}, scriptContent)
|
||||
|
||||
if (!dashboardData) {
|
||||
await this.bot.browser.utils.captureDiagnostics(target, 'dashboard-data-parse').catch(()=>{})
|
||||
throw this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', 'Unable to parse dashboard script', 'error')
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Page } from 'rebrowser-playwright'
|
||||
import { load } from 'cheerio'
|
||||
|
||||
import { MicrosoftRewardsBot } from '../index'
|
||||
import { captureDiagnostics as captureSharedDiagnostics } from '../util/Diagnostics'
|
||||
|
||||
|
||||
export default class BrowserUtil {
|
||||
@@ -106,39 +107,8 @@ export default class BrowserUtil {
|
||||
*/
|
||||
async humanizePage(page: Page): Promise<void> {
|
||||
try {
|
||||
const h = this.bot.config?.humanization || {}
|
||||
if (h.enabled === false) return
|
||||
const moveProb = typeof h.gestureMoveProb === 'number' ? h.gestureMoveProb : 0.4
|
||||
const scrollProb = typeof h.gestureScrollProb === 'number' ? h.gestureScrollProb : 0.2
|
||||
// minor mouse move
|
||||
if (Math.random() < moveProb) {
|
||||
const x = Math.floor(Math.random() * 30) + 5
|
||||
const y = Math.floor(Math.random() * 20) + 3
|
||||
await page.mouse.move(x, y, { steps: 2 }).catch(() => { })
|
||||
}
|
||||
// tiny scroll
|
||||
if (Math.random() < scrollProb) {
|
||||
const dy = (Math.random() < 0.5 ? 1 : -1) * (Math.floor(Math.random() * 150) + 50)
|
||||
await page.mouse.wheel(0, dy).catch(() => { })
|
||||
}
|
||||
// Random short wait; override via humanization.actionDelay
|
||||
const range = h.actionDelay
|
||||
if (range && typeof range.min !== 'undefined' && typeof range.max !== 'undefined') {
|
||||
try {
|
||||
const ms = (await import('ms')).default
|
||||
const min = typeof range.min === 'number' ? range.min : ms(String(range.min))
|
||||
const max = typeof range.max === 'number' ? range.max : ms(String(range.max))
|
||||
if (typeof min === 'number' && typeof max === 'number' && max >= min) {
|
||||
await this.bot.utils.wait(this.bot.utils.randomNumber(Math.max(0, min), Math.min(max, 5000)))
|
||||
} else {
|
||||
await this.bot.utils.wait(this.bot.utils.randomNumber(150, 450))
|
||||
}
|
||||
} catch {
|
||||
await this.bot.utils.wait(this.bot.utils.randomNumber(150, 450))
|
||||
}
|
||||
} else {
|
||||
await this.bot.utils.wait(this.bot.utils.randomNumber(150, 450))
|
||||
}
|
||||
await this.bot.humanizer.microGestures(page)
|
||||
await this.bot.humanizer.actionPause()
|
||||
} catch { /* swallow */ }
|
||||
}
|
||||
|
||||
@@ -147,33 +117,7 @@ export default class BrowserUtil {
|
||||
* Files are written under ./reports/<date>/ with a safe label.
|
||||
*/
|
||||
async captureDiagnostics(page: Page, label: string): Promise<void> {
|
||||
try {
|
||||
const cfg = this.bot.config?.diagnostics || {}
|
||||
if (cfg.enabled === false) return
|
||||
const maxPerRun = typeof cfg.maxPerRun === 'number' ? cfg.maxPerRun : 8
|
||||
if (!this.bot.tryReserveDiagSlot(maxPerRun)) return
|
||||
|
||||
const safe = label.replace(/[^a-z0-9-_]/gi, '_').slice(0, 64)
|
||||
const now = new Date()
|
||||
const day = `${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,'0')}-${String(now.getDate()).padStart(2,'0')}`
|
||||
const baseDir = `${process.cwd()}/reports/${day}`
|
||||
const fs = await import('fs')
|
||||
const path = await import('path')
|
||||
if (!fs.existsSync(baseDir)) fs.mkdirSync(baseDir, { recursive: true })
|
||||
const ts = `${String(now.getHours()).padStart(2,'0')}${String(now.getMinutes()).padStart(2,'0')}${String(now.getSeconds()).padStart(2,'0')}`
|
||||
const shot = path.join(baseDir, `${ts}_${safe}.png`)
|
||||
const htmlPath = path.join(baseDir, `${ts}_${safe}.html`)
|
||||
if (cfg.saveScreenshot !== false) {
|
||||
await page.screenshot({ path: shot }).catch(()=>{})
|
||||
}
|
||||
if (cfg.saveHtml !== false) {
|
||||
const html = await page.content().catch(()=> '<html></html>')
|
||||
fs.writeFileSync(htmlPath, html)
|
||||
}
|
||||
this.bot.log(this.bot.isMobile, 'DIAG', `Saved diagnostics to ${shot} and ${htmlPath}`)
|
||||
} catch (e) {
|
||||
this.bot.log(this.bot.isMobile, 'DIAG', `Failed to capture diagnostics: ${e instanceof Error ? e.message : e}`, 'warn')
|
||||
}
|
||||
await captureSharedDiagnostics(this.bot, page, label)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user