* 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:
Light
2025-10-11 16:54:07 +02:00
committed by GitHub
parent 3e499be8a9
commit dc7e122bce
34 changed files with 1571 additions and 356 deletions

View File

@@ -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

View File

@@ -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')
}

View File

@@ -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)
}
}