diff --git a/src/functions/activities/Search.ts b/src/functions/activities/Search.ts index 74335ae..33f9d42 100644 --- a/src/functions/activities/Search.ts +++ b/src/functions/activities/Search.ts @@ -228,6 +228,7 @@ export class Search extends Workers { // Try dismissing overlays before interacting await this.bot.browser.utils.tryDismissAllMessages(searchPage) await this.bot.utils.wait(200) + await this.bot.browser.utils.humanizePage(searchPage) let navigatedDirectly = false try { @@ -311,7 +312,7 @@ export class Search extends Workers { data: `f.req=[[[i0OFE,"[null, null, \\"${geoLocale.toUpperCase()}\\", 0, null, 48]"]]]` } - const response = await this.bot.axios.request(request, this.bot.config.proxy.proxyGoogleTrends) + const response = await this.bot.axios.request(request, !this.bot.config.proxy.proxyGoogleTrends) const rawText = response.data const trendsData = this.extractJsonFromResponse(rawText) @@ -368,7 +369,7 @@ export class Search extends Workers { } } - const response = await this.bot.axios.request(request, this.bot.config.proxy.proxyBingTerms) + const response = await this.bot.axios.request(request, !this.bot.config.proxy.proxyBingTerms) return response.data[1] as string[] } catch (error) { diff --git a/src/index.ts b/src/index.ts index 930f44f..9283d2f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ -import chalk from 'chalk' +import type { AxiosRequestConfig } from 'axios' +import chalk from 'chalk' import { spawn } from 'child_process' import type { Worker } from 'cluster' import cluster from 'cluster' @@ -75,6 +76,7 @@ export class MicrosoftRewardsBot { this.accounts = [] this.utils = new Util() this.config = loadConfig() + this.enforceHumanization() // JobState will be initialized in initialize() method after validation this.browser = { func: new BrowserFunc(this), @@ -84,14 +86,6 @@ export class MicrosoftRewardsBot { this.workers = new Workers(this) this.humanizer = new Humanizer(this.utils, this.config.humanization) this.activeWorkers = this.config.clusters - - if (this.config.queryDiversity?.enabled) { - this.queryEngine = new QueryDiversityEngine({ - sources: this.config.queryDiversity.sources, - maxQueriesPerSource: this.config.queryDiversity.maxQueriesPerSource, - cacheMinutes: this.config.queryDiversity.cacheMinutes - }) - } } async initialize() { @@ -199,6 +193,35 @@ export class MicrosoftRewardsBot { } } + private enforceHumanization(): void { + const allowDisable = process.env.ALLOW_HUMANIZATION_OFF === '1' + if (this.config?.humanization?.enabled === false && !allowDisable) { + log('main', 'HUMANIZATION', 'Humanization disabled in config; forcing it on for anti-detection safety (set ALLOW_HUMANIZATION_OFF=1 to override).', 'warn') + this.config.humanization = { ...this.config.humanization, enabled: true } + } + } + + private buildQueryEngine(): QueryDiversityEngine | undefined { + if (!this.config.queryDiversity?.enabled) { + return undefined + } + + const proxyHttpClient = { + request: (config: AxiosRequestConfig) => this.axios.request(config) + } + + const logger = (source: string, message: string, level: 'info' | 'warn' | 'error' = 'info') => { + const mapped = level === 'error' ? 'error' : level === 'warn' ? 'warn' : 'log' + this.log(this.isMobile, source, message, mapped) + } + + return new QueryDiversityEngine({ + sources: this.config.queryDiversity.sources, + maxQueriesPerSource: this.config.queryDiversity.maxQueriesPerSource, + cacheMinutes: this.config.queryDiversity.cacheMinutes + }, logger, proxyHttpClient) + } + async run() { this.printBanner() log('main', 'MAIN', `Bot started with ${this.config.clusters} clusters`) @@ -502,6 +525,7 @@ export class MicrosoftRewardsBot { const banned = { status: false, reason: '' } this.axios = new Axios(account.proxy) + this.queryEngine = this.buildQueryEngine() const verbose = process.env.DEBUG_REWARDS_VERBOSE === '1' if (this.config.dryRun) { @@ -525,6 +549,7 @@ export class MicrosoftRewardsBot { if (this.config.parallel) { const mobileInstance = new MicrosoftRewardsBot(true) mobileInstance.axios = this.axios + mobileInstance.queryEngine = this.queryEngine // IMPROVED: Shared state to track desktop issues for early mobile abort consideration let desktopDetectedIssue = false diff --git a/src/util/network/Axios.ts b/src/util/network/Axios.ts index 97a636c..97fcc8b 100644 --- a/src/util/network/Axios.ts +++ b/src/util/network/Axios.ts @@ -99,9 +99,8 @@ class AxiosClient { // Handle HTTP 407 Proxy Authentication Required if (this.isProxyAuthError(err)) { - // Retry without proxy on auth failure - const bypassInstance = axios.create() - return bypassInstance.request(config) + const msg = err instanceof Error ? err.message : String(err) + throw new Error(`Proxy authentication failed. Direct fallback disabled to avoid IP leakage. Details: ${msg}`) } // Handle retryable network errors @@ -112,9 +111,6 @@ class AxiosClient { await this.sleep(delayMs) continue } - // Last attempt: try without proxy - const bypassInstance = axios.create() - return bypassInstance.request(config) } // Non-retryable error diff --git a/src/util/network/QueryDiversityEngine.ts b/src/util/network/QueryDiversityEngine.ts index b0bdb96..db99bb0 100644 --- a/src/util/network/QueryDiversityEngine.ts +++ b/src/util/network/QueryDiversityEngine.ts @@ -1,6 +1,8 @@ -import axios from 'axios' +import axios, { AxiosRequestConfig, AxiosResponse } from 'axios' import { Util } from '../core/Utils' +type HttpClient = Pick | { request: (config: AxiosRequestConfig) => Promise } + export interface QueryDiversityConfig { sources: Array<'google-trends' | 'reddit' | 'news' | 'wikipedia' | 'local-fallback'> deduplicate: boolean @@ -18,8 +20,9 @@ export class QueryDiversityEngine { private cache: Map = new Map() private util: Util = new Util() private logger?: (source: string, message: string, level?: 'info' | 'warn' | 'error') => void + private httpClient: HttpClient - constructor(config?: Partial, logger?: (source: string, message: string, level?: 'info' | 'warn' | 'error') => void) { + constructor(config?: Partial, logger?: (source: string, message: string, level?: 'info' | 'warn' | 'error'), httpClient?: HttpClient) { const maxQueriesPerSource = Math.max(1, Math.min(config?.maxQueriesPerSource || 10, 50)) const cacheMinutes = Math.max(1, Math.min(config?.cacheMinutes || 30, 1440)) @@ -33,6 +36,7 @@ export class QueryDiversityEngine { cacheMinutes } this.logger = logger + this.httpClient = httpClient || axios } private log(source: string, message: string, level: 'info' | 'warn' | 'error' = 'info'): void { @@ -51,7 +55,7 @@ export class QueryDiversityEngine { timeout?: number }): Promise { try { - const response = await axios({ + const response = await this.httpClient.request({ url, method: config?.method || 'GET', headers: config?.headers || { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }, diff --git a/src/util/notifications/Logger.ts b/src/util/notifications/Logger.ts index c82cb5c..8d2b2ba 100644 --- a/src/util/notifications/Logger.ts +++ b/src/util/notifications/Logger.ts @@ -226,10 +226,19 @@ export function log(isMobile: boolean | 'main', title: string, message: string, type LoggingCfg = { excludeFunc?: string[]; webhookExcludeFunc?: string[]; redactEmails?: boolean } const loggingCfg: LoggingCfg = logging || {} const shouldRedact = !!loggingCfg.redactEmails - const redact = (s: string) => shouldRedact ? s.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig, (m) => { - const [u, d] = m.split('@'); return `${(u || '').slice(0, 2)}***@${d || ''}` - }) : s - const cleanStr = redact(`[${currentTime}] [PID: ${process.pid}] [${type.toUpperCase()}] ${platformText} [${title}] ${message}`) + const redactSensitive = (s: string) => { + const scrubbed = s + .replace(/:\/\/[A-Z0-9._%+-]+:[^@\s]+@/ig, '://***:***@') + .replace(/(token=|apikey=|auth=)[^\s&]+/ig, '$1***') + + if (!shouldRedact) return scrubbed + + return scrubbed.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig, (m) => { + const [u, d] = m.split('@'); return `${(u || '').slice(0, 2)}***@${d || ''}` + }) + } + + const cleanStr = redactSensitive(`[${currentTime}] [PID: ${process.pid}] [${type.toUpperCase()}] ${platformText} [${title}] ${message}`) // Define conditions for sending to NTFY const ntfyConditions = { @@ -293,7 +302,7 @@ export function log(isMobile: boolean | 'main', title: string, message: string, typeColor(`${typeIndicator}`), platformColor(`[${platformText}]`), chalk.bold(`[${title}]`), - iconPart + redact(message) + iconPart + redactSensitive(message) ].join(' ') const applyChalk = color && typeof chalk[color] === 'function' ? chalk[color] as (msg: string) => string : null