Improved interactive mode: added an automatic job reset option for scheduled tasks and improved stealth mode to bypass CAPTCHAs.

This commit is contained in:
2025-11-05 22:44:16 +01:00
parent ee28e92866
commit 0d6ab38b48
4 changed files with 108 additions and 9 deletions

View File

@@ -29,20 +29,23 @@ class Browser {
const envForceHeadless = process.env.FORCE_HEADLESS === '1' const envForceHeadless = process.env.FORCE_HEADLESS === '1'
let headless = envForceHeadless ? true : (this.bot.config.browser?.headless ?? false) let headless = envForceHeadless ? true : (this.bot.config.browser?.headless ?? false)
if (this.bot.isBuyModeEnabled() && !envForceHeadless) { // Buy/Interactive mode: always visible and with enhanced stealth
const isBuyMode = this.bot.isBuyModeEnabled()
if (isBuyMode && !envForceHeadless) {
if (headless !== false) { if (headless !== false) {
const target = this.bot.getBuyModeTarget() const target = this.bot.getBuyModeTarget()
this.bot.log(this.bot.isMobile, 'BROWSER', `Buy mode: forcing headless=false${target ? ` for ${target}` : ''}`, 'warn') this.bot.log(this.bot.isMobile, 'BROWSER', `Interactive mode: forcing headless=false${target ? ` for ${target}` : ''}`, 'warn')
} }
headless = false headless = false
} }
const engineName = 'chromium' const engineName = 'chromium'
this.bot.log(this.bot.isMobile, 'BROWSER', `Launching ${engineName} (headless=${headless})`) this.bot.log(this.bot.isMobile, 'BROWSER', `Launching ${engineName} (headless=${headless}${isBuyMode ? ', stealth-mode=ENHANCED' : ''})`)
const proxyConfig = this.buildPlaywrightProxy(proxy) const proxyConfig = this.buildPlaywrightProxy(proxy)
const isLinux = process.platform === 'linux' const isLinux = process.platform === 'linux'
// Base arguments for stability
const baseArgs = [ const baseArgs = [
'--no-sandbox', '--no-sandbox',
'--mute-audio', '--mute-audio',
@@ -52,7 +55,7 @@ class Browser {
'--ignore-ssl-errors' '--ignore-ssl-errors'
] ]
// Linux stability fixes without detection risk // Linux stability fixes
const linuxStabilityArgs = isLinux ? [ const linuxStabilityArgs = isLinux ? [
'--disable-dev-shm-usage', '--disable-dev-shm-usage',
'--disable-software-rasterizer', '--disable-software-rasterizer',
@@ -60,10 +63,26 @@ class Browser {
'--disk-cache-size=1' '--disk-cache-size=1'
] : [] ] : []
// ENHANCED STEALTH MODE for Buy/Interactive Mode
// These arguments help bypass CAPTCHA and automation detection
const stealthArgs = isBuyMode ? [
'--disable-blink-features=AutomationControlled', // Critical: Hide automation
'--disable-features=IsolateOrigins,site-per-process', // Reduce detection surface
'--disable-site-isolation-trials',
'--disable-web-security', // Allow cross-origin (may help with CAPTCHA)
'--disable-features=VizDisplayCompositor', // Reduce GPU fingerprinting
'--no-first-run',
'--no-default-browser-check',
'--disable-infobars',
'--window-position=0,0',
'--window-size=1920,1080', // Consistent window size
'--start-maximized'
] : []
browser = await playwright.chromium.launch({ browser = await playwright.chromium.launch({
headless, headless,
...(proxyConfig && { proxy: proxyConfig }), ...(proxyConfig && { proxy: proxyConfig }),
args: [...baseArgs, ...linuxStabilityArgs], args: [...baseArgs, ...linuxStabilityArgs, ...stealthArgs],
timeout: isLinux ? 90000 : 60000 timeout: isLinux ? 90000 : 60000
}) })
} catch (e: unknown) { } catch (e: unknown) {
@@ -87,6 +106,8 @@ class Browser {
const globalTimeout = this.bot.config.browser?.globalTimeout ?? 30000 const globalTimeout = this.bot.config.browser?.globalTimeout ?? 30000
context.setDefaultTimeout(typeof globalTimeout === 'number' ? globalTimeout : this.bot.utils.stringToMs(globalTimeout)) context.setDefaultTimeout(typeof globalTimeout === 'number' ? globalTimeout : this.bot.utils.stringToMs(globalTimeout))
const isBuyMode = this.bot.isBuyModeEnabled()
try { try {
context.on('page', async (page) => { context.on('page', async (page) => {
try { try {
@@ -96,6 +117,7 @@ class Browser {
await page.setViewportSize(viewport) await page.setViewportSize(viewport)
// Standard styling
await page.addInitScript(() => { await page.addInitScript(() => {
try { try {
const style = document.createElement('style') const style = document.createElement('style')
@@ -109,6 +131,63 @@ class Browser {
document.documentElement.appendChild(style) document.documentElement.appendChild(style)
} catch {/* ignore */} } catch {/* ignore */}
}) })
// ENHANCED ANTI-DETECTION for Buy/Interactive Mode
if (isBuyMode) {
await page.addInitScript(`
// Override navigator.webdriver (critical for CAPTCHA bypass)
Object.defineProperty(Object.getPrototypeOf(navigator), 'webdriver', {
get: () => false
});
// Add chrome runtime (looks more human)
Object.defineProperty(window, 'chrome', {
writable: true,
enumerable: true,
configurable: false,
value: { runtime: {} }
});
// Add plugins (looks more human)
Object.defineProperty(navigator, 'plugins', {
get: () => [
{ name: 'Chrome PDF Plugin' },
{ name: 'Chrome PDF Viewer' },
{ name: 'Native Client' }
]
});
// Languages
Object.defineProperty(navigator, 'languages', {
get: () => ['en-US', 'en', 'fr']
});
// Hide automation markers
['__nightmare', '__playwright', '__pw_manual', '__webdriver_script_fn', 'webdriver'].forEach(prop => {
try {
if (prop in window) delete window[prop];
} catch {}
});
// Override permissions to avoid detection
const originalPermissionsQuery = window.navigator.permissions.query;
window.navigator.permissions.query = function(params) {
if (params.name === 'notifications') {
return Promise.resolve({
state: Notification.permission,
name: 'notifications',
onchange: null,
addEventListener: () => {},
removeEventListener: () => {},
dispatchEvent: () => true
});
}
return originalPermissionsQuery.call(this, params);
};
`)
this.bot.log(this.bot.isMobile, 'BROWSER', '🛡️ Enhanced stealth mode activated (anti-CAPTCHA)', 'log', 'green')
}
} catch (e) { } catch (e) {
this.bot.log(this.bot.isMobile, 'BROWSER', `Page setup warning: ${e instanceof Error ? e.message : String(e)}`, 'warn') this.bot.log(this.bot.isMobile, 'BROWSER', `Page setup warning: ${e instanceof Error ? e.message : String(e)}`, 'warn')
} }

View File

@@ -25,7 +25,8 @@
}, },
"jobState": { "jobState": {
"enabled": true, "enabled": true,
"dir": "" "dir": "",
"autoResetOnComplete": true // Set to true to automatically rerun all accounts without prompting (for scheduled tasks)
}, },
// Tasks // Tasks

View File

@@ -171,9 +171,27 @@ export class MicrosoftRewardsBot {
} }
private async promptResetJobState(): Promise<boolean> { private async promptResetJobState(): Promise<boolean> {
// Skip prompt in non-interactive environments (Docker, CI, scheduled tasks) // Check if auto-reset is enabled in config (for scheduled tasks)
if (!process.stdin.isTTY) { if (this.config.jobState?.autoResetOnComplete === true) {
log('main','TASK','Non-interactive environment detected - keeping job state', 'warn') log('main','TASK','Auto-reset enabled (jobState.autoResetOnComplete=true) - resetting and rerunning all accounts', 'log', 'green')
return true
}
// Check environment variable override
const envAutoReset = process.env.REWARDS_AUTO_RESET_JOBSTATE
if (envAutoReset === '1' || envAutoReset?.toLowerCase() === 'true') {
log('main','TASK','Auto-reset enabled (REWARDS_AUTO_RESET_JOBSTATE) - resetting and rerunning all accounts', 'log', 'green')
return true
}
// Detect non-interactive environments more reliably
const isNonInteractive = !process.stdin.isTTY ||
process.env.CI === 'true' ||
process.env.DOCKER === 'true' ||
process.env.SCHEDULED_TASK === 'true'
if (isNonInteractive) {
log('main','TASK','Non-interactive environment detected - keeping job state (set jobState.autoResetOnComplete=true to auto-rerun)', 'warn')
return false return false
} }

View File

@@ -156,6 +156,7 @@ export interface ConfigJobState {
enabled?: boolean; // default true enabled?: boolean; // default true
dir?: string; // base directory; defaults to <sessionPath>/job-state dir?: string; // base directory; defaults to <sessionPath>/job-state
skipCompletedAccounts?: boolean; // if true (default), skip accounts already completed for the day skipCompletedAccounts?: boolean; // if true (default), skip accounts already completed for the day
autoResetOnComplete?: boolean; // if true, automatically reset and rerun without prompting (useful for scheduled tasks)
} }
// Live logging configuration // Live logging configuration