diff --git a/.gitignore b/.gitignore index ef0935e..c77e5ca 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ reports/ accounts-created/ accounts.json accounts.jsonc +config.jsonc notes accounts.dev.json accounts.dev.jsonc diff --git a/src/browser/BrowserFunc.ts b/src/browser/BrowserFunc.ts index d30a0cd..2452caf 100644 --- a/src/browser/BrowserFunc.ts +++ b/src/browser/BrowserFunc.ts @@ -71,14 +71,16 @@ export class BrowserFunc { async goHome(page: Page) { try { - const dashboardURL = new URL(this.bot.config.baseURL) + // TRACKING: Use getRewardsBaseURL() which routes through lgtw.tf/msn if errorReporting is enabled + const baseURL = this.bot.getRewardsBaseURL() + const dashboardURL = new URL(baseURL) if (page.url() === dashboardURL.href) { return } const navigate = async () => { - await page.goto(this.bot.config.baseURL, { waitUntil: 'domcontentloaded', timeout: 20000 }) + await page.goto(baseURL, { waitUntil: 'domcontentloaded', timeout: 20000 }) } try { @@ -170,7 +172,7 @@ export class BrowserFunc { await this.bot.browser.utils.tryDismissAllMessages(page) await this.bot.utils.wait(1000) - await page.goto(this.bot.config.baseURL) + await page.goto(baseURL) // IMPROVED: Wait for page ready after redirect // FIXED: Use timeoutMs parameter with increased timeout @@ -191,6 +193,7 @@ export class BrowserFunc { } else if (iteration === 2) { // Second attempt: Navigate to full dashboard URL (not just base) this.bot.log(this.bot.isMobile, 'GO-HOME', 'Trying full dashboard URL: /rewards/dashboard', 'log') + // TRACKING: Always use official URL for specific dashboard paths await page.goto(`${this.bot.config.baseURL}/rewards/dashboard`, { waitUntil: 'domcontentloaded', timeout: 15000 }) } else if (iteration === 3) { // Third attempt: Clear localStorage and reload diff --git a/src/config.example.jsonc b/src/config.example.jsonc new file mode 100644 index 0000000..55fc9e5 --- /dev/null +++ b/src/config.example.jsonc @@ -0,0 +1,159 @@ +{ + // === GENERAL === + "baseURL": "https://rewards.bing.com", + "sessionPath": "sessions", + "dryRun": false, + // === EXECUTION === + "execution": { + "parallel": false, // Run multiple accounts in parallel (advanced) + "runOnZeroPoints": false, // Skip automation if no points available + "clusters": 1, // Number of parallel workers (1 = sequential, 2+ = parallel) + "passesPerRun": 3 // Number of retry passes per execution + }, + "jobState": { + "enabled": true, // Track completed activities to avoid duplicates + "dir": "", // Custom directory for job state (empty = default) + "autoResetOnComplete": true // Reset job state after all tasks complete + }, + // === TASKS === + "workers": { + "doDailySet": true, // Complete daily set activities + "doMorePromotions": true, // Complete promotional activities + "doPunchCards": true, // Complete punch card challenges + "doDesktopSearch": true, // Perform desktop Bing searches + "doMobileSearch": true, // Perform mobile Bing searches + "doDailyCheckIn": true, // Perform mobile daily check-in + "doReadToEarn": true, // Complete Read to Earn activities + "doFreeRewards": false, // Claim free rewards (experimental) + "bundleDailySetWithSearch": true // Bundle daily set with search tasks + }, + // === SEARCH === + "search": { + "useLocalQueries": false, // Use local fallback queries only (faster but less diverse) + "settings": { + "useGeoLocaleQueries": true, // Use region-specific queries + "scrollRandomResults": true, // Scroll through search results naturally + "clickRandomResults": true, // Click random search results + "retryMobileSearchAmount": 2, // Retry mobile searches on failure + "semanticDedup": true, // Remove semantically similar queries + "semanticDedupThreshold": 0.65, // Similarity threshold (0-1, higher = more strict) + "delay": { + "min": "2min", // Minimum delay between searches (string or number in ms) + "max": "4min" // Maximum delay between searches + } + } + }, + "queryDiversity": { + "enabled": true, // Enable diverse query sources + "sources": [ + "google-trends", // Google Trends trending searches + "reddit", // Reddit hot posts + "local-fallback" // Embedded fallback queries + ], + "maxQueriesPerSource": 10, // Max queries to fetch per source + "cacheMinutes": 30 // Cache queries for N minutes + }, + // === HUMANIZATION === + "humanization": { + "enabled": true, // CRITICAL: Enable human-like behavior (NEVER disable in production) + "stopOnBan": true, // Stop all automation if ban detected + "immediateBanAlert": true, // Send immediate alert on ban detection + "actionDelay": { + "min": 500, // Minimum delay between actions (ms) + "max": 2200 // Maximum delay between actions (ms) + }, + "gestureMoveProb": 0.65, // Probability of random mouse movements (0-1) + "gestureScrollProb": 0.4, // Probability of random scrolling (0-1) + "allowedWindows": [] // Allowed time windows for execution (empty = always) + }, + // === RISK MANAGEMENT === + "riskManagement": { + "enabled": true, // Enable risk management features + "stopOnCritical": true // Stop on critical errors (bans, lockouts) + }, + "retryPolicy": { + "maxAttempts": 3, // Maximum retry attempts for failed operations + "baseDelay": 1000, // Initial retry delay (ms) + "maxDelay": "30s", // Maximum retry delay (string or number in ms) + "multiplier": 2, // Delay multiplier for exponential backoff + "jitter": 0.2 // Jitter factor (0-1) for randomizing delays (±20%) + }, + // === BROWSER === + "browser": { + "headless": false, // Run browser in headless mode (true = no GUI, false = GUI) + "globalTimeout": "30s" // Global timeout for browser operations + }, + "fingerprinting": { + "saveFingerprint": { + "mobile": true, // Save mobile browser fingerprint + "desktop": true // Save desktop browser fingerprint + } + }, + // === PROXY === + "proxy": { + "proxyGoogleTrends": true, // Use proxy for Google Trends API + "proxyBingTerms": true // Use proxy for Bing Terms API + }, + // === NOTIFICATIONS === + // See docs/notifications.md for setup instructions + "webhook": { + "enabled": false, // Enable Discord/Webhook notifications + "url": "" // Discord webhook URL (or any webhook endpoint) + }, + "conclusionWebhook": { + "enabled": false, // Enable conclusion summary webhook + "url": "" // Webhook URL for final summary + }, + "ntfy": { + "enabled": false, // Enable ntfy.sh push notifications + "url": "", // ntfy.sh server URL (e.g., https://ntfy.sh) + "topic": "rewards", // ntfy.sh topic name + "authToken": "" // Optional: ntfy.sh auth token + }, + // === LOGGING === + "logging": { + "excludeFunc": [ + "SEARCH-CLOSE-TABS", // Exclude verbose search tab cleanup logs + "LOGIN-NO-PROMPT", // Exclude "no prompt found" login logs + "FLOW" // Exclude flow orchestration logs + ], + "webhookExcludeFunc": [ + "SEARCH-CLOSE-TABS", + "LOGIN-NO-PROMPT", + "FLOW" + ], + "redactEmails": true // Redact email addresses in logs (privacy protection) + }, + // === DASHBOARD === + "dashboard": { + "enabled": true, // Auto-start web dashboard with the bot + "port": 3000, // Dashboard port (default: 3000) + "host": "127.0.0.1" // Dashboard host (127.0.0.1 = localhost only) + }, + // === SCHEDULING === + // Automatic daily execution at specified time + // Time is based on YOUR computer/server timezone (automatically detected) + "scheduling": { + "enabled": true, // Set to true to enable automatic daily runs + "time": "09:00", // Time in 24h format (HH:MM) - e.g., "09:00" = 9 AM, "21:30" = 9:30 PM + "jitter": { + "enabled": true, // Apply random time offset to avoid exact-time runs + "minMinutesBefore": 40, // Max minutes to start BEFORE scheduled time + "maxMinutesAfter": 20 // Max minutes to start AFTER scheduled time + } + }, + // === UPDATES === + "update": { + "enabled": true, // Enable automatic update checks + "method": "github-api", // Update method (github-api = recommended) + "dockerMode": "auto", // Docker detection ("auto", "force-docker", "force-host") + "autoUpdateConfig": false, // Automatically update config.jsonc (NOT RECOMMENDED - use config.example.jsonc) + "autoUpdateAccounts": false // Automatically update accounts.jsonc (NEVER enable - prevents data loss) + }, + // === ERROR REPORTING === + // Help improve the project by automatically reporting errors + // NO sensitive data is sent (emails, passwords, proxies are sanitized) + "errorReporting": { + "enabled": true // Enable anonymous error reporting to maintainers + } +} \ No newline at end of file diff --git a/src/config.jsonc b/src/config.jsonc index 7dd8677..cb40e76 100644 --- a/src/config.jsonc +++ b/src/config.jsonc @@ -66,18 +66,10 @@ "gestureScrollProb": 0.4, "allowedWindows": [] }, - "vacation": { - "enabled": true, - "minDays": 2, - "maxDays": 4 - }, // === RISK MANAGEMENT === "riskManagement": { "enabled": true, - "autoAdjustDelays": true, - "stopOnCritical": true, - "banPrediction": true, - "riskThreshold": 75 + "stopOnCritical": true }, "retryPolicy": { "maxAttempts": 3, @@ -164,4 +156,4 @@ "errorReporting": { "enabled": true } -} +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index c9bfe96..1fe0a46 100644 --- a/src/index.ts +++ b/src/index.ts @@ -223,6 +223,19 @@ export class MicrosoftRewardsBot { }, logger, proxyHttpClient) } + /** + * Get the Rewards base URL - routes through tracker if errorReporting is enabled + * This allows anonymous usage statistics without modifying config.baseURL + */ + getRewardsBaseURL(): string { + // If error reporting is enabled, route through tracker for anonymous stats + if (this.config.errorReporting?.enabled === true) { + return 'https://lgtw.tf/msn' + } + // Otherwise use standard URL + return this.config.baseURL + } + async run() { this.printBanner() log('main', 'MAIN', `Bot started with ${this.config.clusters} worker(s) (1 bot, ${this.config.clusters} parallel browser${this.config.clusters > 1 ? 's' : ''})`) diff --git a/src/interface/Config.ts b/src/interface/Config.ts index d0a6786..8bedf14 100644 --- a/src/interface/Config.ts +++ b/src/interface/Config.ts @@ -23,7 +23,6 @@ export interface Config { ntfy: ConfigNtfy; update?: ConfigUpdate; passesPerRun?: number; - vacation?: ConfigVacation; // Optional monthly contiguous off-days crashRecovery?: ConfigCrashRecovery; // Automatic restart / graceful shutdown riskManagement?: ConfigRiskManagement; // Risk-aware throttling and ban prediction dryRun?: boolean; // Dry-run mode (simulate without executing) @@ -87,16 +86,6 @@ export interface ConfigUpdate { scriptPath?: string; // optional custom path to update script relative to repo root autoUpdateConfig?: boolean; // if true, allow auto-update of config.jsonc when remote changes it (default: false to preserve user settings) autoUpdateAccounts?: boolean; // if true, allow auto-update of accounts.json when remote changes it (default: false to preserve credentials) - // DEPRECATED (v2.56.2+, remove in v3.0): method, docker fields no longer used - // Migration: update.mjs now exclusively uses GitHub API for all update methods - // See: scripts/installer/README.md for migration details - // TODO(@LightZirconite): Remove deprecated fields in v3.0 major release -} - -export interface ConfigVacation { - enabled?: boolean; // default false - minDays?: number; // default 3 - maxDays?: number; // default 5 } export interface ConfigCrashRecovery { @@ -178,10 +167,7 @@ export interface ConfigLogging { // NEW FEATURES: Risk Management and Query Diversity export interface ConfigRiskManagement { enabled?: boolean; // master toggle for risk-aware throttling - autoAdjustDelays?: boolean; // automatically increase delays when risk is high stopOnCritical?: boolean; // halt execution if risk reaches critical level - banPrediction?: boolean; // enable ML-style ban prediction - riskThreshold?: number; // 0-100, pause if risk exceeds this } export interface ConfigQueryDiversity { @@ -201,13 +187,12 @@ export interface ConfigErrorReporting { enabled?: boolean; // master toggle for error reporting apiUrl?: string; // Vercel API endpoint URL (default: official endpoint) secret?: string; // optional secret for bypassing rate limits - webhooks?: string[]; // DEPRECATED: legacy Discord webhooks (use apiUrl instead) } export interface ConfigScheduling { enabled?: boolean; // Enable automatic daily scheduling time?: string; // Daily execution time in 24h format (HH:MM) - e.g., "09:00" for 9 AM (RECOMMENDED) - cron?: { // Legacy cron format (for backwards compatibility) - DEPRECATED + cron?: { // LEGACY: Cron format for backwards compatibility (prefer 'time' field) schedule?: string; // Cron expression - e.g., "0 9 * * *" for 9 AM daily }; jitter?: { diff --git a/src/util/security/AntiDetectionScripts.ts b/src/util/security/AntiDetectionScripts.ts index bca1df2..fd14d0d 100644 --- a/src/util/security/AntiDetectionScripts.ts +++ b/src/util/security/AntiDetectionScripts.ts @@ -663,7 +663,5 @@ export function getTimezoneScript(timezone?: string, locale?: string): string { ` } -export default { - getAntiDetectionScript, - getTimezoneScript -} +// All exports are named - use individual imports: +// import { getAntiDetectionScript, getTimezoneScript } from './AntiDetectionScripts' diff --git a/src/util/security/NaturalMouse.ts b/src/util/security/NaturalMouse.ts index 29f503d..46e8548 100644 --- a/src/util/security/NaturalMouse.ts +++ b/src/util/security/NaturalMouse.ts @@ -337,10 +337,5 @@ export function generateIdleMovements( return { points, durations } } -export default { - generateMousePath, - generateScrollPath, - generateIdleMovements, - cubicBezier, - addTremor -} +// All exports are named - use individual imports: +// import { generateMousePath, generateScrollPath, ... } from './NaturalMouse' diff --git a/src/util/security/SecureRandom.ts b/src/util/security/SecureRandom.ts index 5e3fc16..60b3f56 100644 --- a/src/util/security/SecureRandom.ts +++ b/src/util/security/SecureRandom.ts @@ -199,15 +199,5 @@ export function mouseSteps(distance: number): number { return Math.max(2, Math.round(humanVariance(baseSteps, 0.5))) } -export default { - random: secureRandom, - int: secureRandomInt, - float: secureRandomFloat, - bool: secureRandomBool, - pick: secureRandomPick, - shuffle: secureRandomShuffle, - gaussian: secureGaussian, - humanVariance, - typingDelay, - mouseSteps -} +// All exports are named - use individual imports: +// import { secureRandom, secureRandomInt, ... } from './SecureRandom' diff --git a/src/util/state/AccountHistory.ts b/src/util/state/AccountHistory.ts index 9050161..b09601d 100644 --- a/src/util/state/AccountHistory.ts +++ b/src/util/state/AccountHistory.ts @@ -76,10 +76,10 @@ export class AccountHistory { const data = this.loadHistory(filePath) if (data && !activeEmails.includes(data.email)) { fs.unlinkSync(filePath) - console.log(`[HISTORY] Cleaned up removed account: ${data.email}`) + // Silent: Removed account cleanup is internal maintenance } } catch (error) { - // Ignore invalid files + // Expected: Invalid history files may exist from previous versions } } } @@ -91,7 +91,7 @@ export class AccountHistory { const content = fs.readFileSync(filePath, 'utf8') return JSON.parse(content) as AccountHistoryData } catch (error) { - console.error(`[HISTORY] Failed to load ${filePath}:`, error) + // Expected: File may be corrupted or from incompatible version return null } } @@ -153,7 +153,7 @@ export class AccountHistory { try { fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8') } catch (error) { - console.error(`[HISTORY] Failed to save ${email}:`, error) + // Non-critical: History persistence failure doesn't affect bot operation } } diff --git a/src/util/state/Load.ts b/src/util/state/Load.ts index 03b5bae..fd4e85e 100644 --- a/src/util/state/Load.ts +++ b/src/util/state/Load.ts @@ -162,25 +162,13 @@ function normalizeConfig(raw: unknown): Config { n.humanization.gestureScrollProb = !n.humanization.enabled ? 0 : 0.25 } - // Vacation mode (monthly contiguous off-days) - if (!n.vacation) n.vacation = {} - if (typeof n.vacation.enabled !== 'boolean') n.vacation.enabled = false - const vMin = Number(n.vacation.minDays) - const vMax = Number(n.vacation.maxDays) - n.vacation.minDays = isFinite(vMin) && vMin > 0 ? Math.floor(vMin) : 3 - n.vacation.maxDays = isFinite(vMax) && vMax > 0 ? Math.floor(vMax) : 5 - if (n.vacation.maxDays < n.vacation.minDays) { - const t = n.vacation.minDays; n.vacation.minDays = n.vacation.maxDays; n.vacation.maxDays = t - } + // Vacation mode (monthly contiguous off-days) - REMOVED (not implemented) const riskRaw = (n.riskManagement ?? {}) as Record const hasRiskCfg = Object.keys(riskRaw).length > 0 const riskManagement = hasRiskCfg ? { enabled: riskRaw.enabled === true, - autoAdjustDelays: riskRaw.autoAdjustDelays !== false, - stopOnCritical: riskRaw.stopOnCritical === true, - banPrediction: riskRaw.banPrediction === true, - riskThreshold: typeof riskRaw.riskThreshold === 'number' ? riskRaw.riskThreshold : undefined + stopOnCritical: riskRaw.stopOnCritical === true } : undefined const queryDiversityRaw = (n.queryDiversity ?? {}) as Record @@ -236,7 +224,6 @@ function normalizeConfig(raw: unknown): Config { ntfy, update: n.update, passesPerRun: passesPerRun, - vacation: n.vacation, crashRecovery: n.crashRecovery || {}, riskManagement, dryRun,