mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-10 01:06:17 +00:00
feat: implement fingerprint validation and age check to enhance anti-detection measures
This commit is contained in:
@@ -6,6 +6,7 @@ import { MicrosoftRewardsBot } from '../index'
|
|||||||
import { AccountProxy } from '../interface/Account'
|
import { AccountProxy } from '../interface/Account'
|
||||||
import { updateFingerprintUserAgent } from '../util/browser/UserAgent'
|
import { updateFingerprintUserAgent } from '../util/browser/UserAgent'
|
||||||
import { loadSessionData, saveFingerprintData } from '../util/state/Load'
|
import { loadSessionData, saveFingerprintData } from '../util/state/Load'
|
||||||
|
import { logFingerprintValidation, validateFingerprintConsistency } from '../util/validation/FingerprintValidator'
|
||||||
|
|
||||||
class Browser {
|
class Browser {
|
||||||
private bot: MicrosoftRewardsBot
|
private bot: MicrosoftRewardsBot
|
||||||
@@ -40,14 +41,33 @@ class Browser {
|
|||||||
|
|
||||||
const isLinux = process.platform === 'linux'
|
const isLinux = process.platform === 'linux'
|
||||||
|
|
||||||
// Base arguments for stability
|
// CRITICAL: Anti-detection Chromium arguments
|
||||||
const baseArgs = [
|
const baseArgs = [
|
||||||
'--no-sandbox',
|
'--no-sandbox',
|
||||||
'--mute-audio',
|
'--mute-audio',
|
||||||
'--disable-setuid-sandbox',
|
'--disable-setuid-sandbox',
|
||||||
'--ignore-certificate-errors',
|
'--ignore-certificate-errors',
|
||||||
'--ignore-certificate-errors-spki-list',
|
'--ignore-certificate-errors-spki-list',
|
||||||
'--ignore-ssl-errors'
|
'--ignore-ssl-errors',
|
||||||
|
// ANTI-DETECTION: Disable blink features that expose automation
|
||||||
|
'--disable-blink-features=AutomationControlled',
|
||||||
|
// ANTI-DETECTION: Disable automation extensions
|
||||||
|
'--disable-extensions',
|
||||||
|
// ANTI-DETECTION: Start maximized (humans rarely start in specific window sizes)
|
||||||
|
'--start-maximized',
|
||||||
|
// ANTI-DETECTION: Disable save password bubble
|
||||||
|
'--disable-save-password-bubble',
|
||||||
|
// ANTI-DETECTION: Disable background timer throttling
|
||||||
|
'--disable-background-timer-throttling',
|
||||||
|
'--disable-backgrounding-occluded-windows',
|
||||||
|
'--disable-renderer-backgrounding',
|
||||||
|
// ANTI-DETECTION: Disable infobars
|
||||||
|
'--disable-infobars',
|
||||||
|
// PERFORMANCE: Disable unnecessary features
|
||||||
|
'--disable-breakpad',
|
||||||
|
'--disable-component-update',
|
||||||
|
'--no-first-run',
|
||||||
|
'--no-default-browser-check'
|
||||||
]
|
]
|
||||||
|
|
||||||
// Linux stability fixes
|
// Linux stability fixes
|
||||||
@@ -80,6 +100,16 @@ class Browser {
|
|||||||
|
|
||||||
const sessionData = await loadSessionData(this.bot.config.sessionPath, email, this.bot.isMobile, saveFingerprint)
|
const sessionData = await loadSessionData(this.bot.config.sessionPath, email, this.bot.isMobile, saveFingerprint)
|
||||||
const fingerprint = sessionData.fingerprint ? sessionData.fingerprint : await this.generateFingerprint()
|
const fingerprint = sessionData.fingerprint ? sessionData.fingerprint : await this.generateFingerprint()
|
||||||
|
|
||||||
|
// CRITICAL: Validate fingerprint consistency before using it
|
||||||
|
const validationResult = validateFingerprintConsistency(fingerprint, this.bot.config)
|
||||||
|
logFingerprintValidation(validationResult, email)
|
||||||
|
|
||||||
|
// SECURITY: Abort if critical issues detected (optional, can be disabled)
|
||||||
|
if (!validationResult.valid && this.bot.config.riskManagement?.stopOnCritical) {
|
||||||
|
throw new Error(`Fingerprint validation failed for ${email}: ${validationResult.criticalIssues.join(', ')}`)
|
||||||
|
}
|
||||||
|
|
||||||
const context = await newInjectedContext(browser as unknown as import('playwright').Browser, { fingerprint: fingerprint })
|
const context = await newInjectedContext(browser as unknown as import('playwright').Browser, { fingerprint: fingerprint })
|
||||||
|
|
||||||
const globalTimeout = this.bot.config.browser?.globalTimeout ?? 30000
|
const globalTimeout = this.bot.config.browser?.globalTimeout ?? 30000
|
||||||
@@ -88,14 +118,153 @@ class Browser {
|
|||||||
try {
|
try {
|
||||||
context.on('page', async (page) => {
|
context.on('page', async (page) => {
|
||||||
try {
|
try {
|
||||||
|
// IMPROVED: Randomized viewport sizes to avoid fingerprinting
|
||||||
|
// Fixed sizes are detectable bot patterns
|
||||||
const viewport = this.bot.isMobile
|
const viewport = this.bot.isMobile
|
||||||
? { width: 390, height: 844 }
|
? {
|
||||||
: { width: 1280, height: 800 }
|
// Mobile: Vary between common phone screen sizes
|
||||||
|
width: 360 + Math.floor(Math.random() * 60), // 360-420px
|
||||||
|
height: 640 + Math.floor(Math.random() * 256) // 640-896px
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
// Desktop: Vary between common desktop resolutions
|
||||||
|
width: 1280 + Math.floor(Math.random() * 640), // 1280-1920px
|
||||||
|
height: 720 + Math.floor(Math.random() * 360) // 720-1080px
|
||||||
|
}
|
||||||
|
|
||||||
await page.setViewportSize(viewport)
|
await page.setViewportSize(viewport)
|
||||||
|
|
||||||
// Standard styling
|
// CRITICAL: Advanced anti-detection scripts (MUST run before page load)
|
||||||
await page.addInitScript(() => {
|
await page.addInitScript(() => {
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// ANTI-DETECTION LAYER 1: Remove automation indicators
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// CRITICAL: Remove navigator.webdriver (biggest bot indicator)
|
||||||
|
try {
|
||||||
|
Object.defineProperty(navigator, 'webdriver', {
|
||||||
|
get: () => undefined,
|
||||||
|
configurable: true
|
||||||
|
})
|
||||||
|
} catch { /* Already defined */ }
|
||||||
|
|
||||||
|
// CRITICAL: Mask Chrome DevTools Protocol detection
|
||||||
|
// Microsoft checks for window.chrome.runtime
|
||||||
|
try {
|
||||||
|
// @ts-ignore - window.chrome is intentionally injected
|
||||||
|
if (!window.chrome) {
|
||||||
|
// @ts-ignore
|
||||||
|
window.chrome = {}
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
if (!window.chrome.runtime) {
|
||||||
|
// @ts-ignore
|
||||||
|
window.chrome.runtime = {
|
||||||
|
// @ts-ignore
|
||||||
|
connect: () => { },
|
||||||
|
// @ts-ignore
|
||||||
|
sendMessage: () => { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch { /* Chrome object may be frozen */ }
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// ANTI-DETECTION LAYER 2: WebGL & Canvas fingerprint randomization
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// CRITICAL: Add noise to Canvas fingerprinting
|
||||||
|
// Microsoft uses Canvas to detect identical browser instances
|
||||||
|
try {
|
||||||
|
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL
|
||||||
|
const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData
|
||||||
|
|
||||||
|
// Random noise generator (consistent per page load, different per session)
|
||||||
|
const noise = Math.random() * 0.0001
|
||||||
|
|
||||||
|
HTMLCanvasElement.prototype.toDataURL = function (...args) {
|
||||||
|
const context = this.getContext('2d')
|
||||||
|
if (context) {
|
||||||
|
// Add imperceptible noise
|
||||||
|
const imageData = context.getImageData(0, 0, this.width, this.height)
|
||||||
|
for (let i = 0; i < imageData.data.length; i += 4) {
|
||||||
|
imageData.data[i] = imageData.data[i]! + noise // R
|
||||||
|
imageData.data[i + 1] = imageData.data[i + 1]! + noise // G
|
||||||
|
imageData.data[i + 2] = imageData.data[i + 2]! + noise // B
|
||||||
|
}
|
||||||
|
context.putImageData(imageData, 0, 0)
|
||||||
|
}
|
||||||
|
return originalToDataURL.apply(this, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
CanvasRenderingContext2D.prototype.getImageData = function (...args) {
|
||||||
|
const imageData = originalGetImageData.apply(this, args)
|
||||||
|
// Add noise to raw pixel data
|
||||||
|
for (let i = 0; i < imageData.data.length; i += 10) {
|
||||||
|
imageData.data[i] = imageData.data[i]! + noise
|
||||||
|
}
|
||||||
|
return imageData
|
||||||
|
}
|
||||||
|
} catch { /* Canvas override may fail in strict mode */ }
|
||||||
|
|
||||||
|
// CRITICAL: WebGL fingerprint randomization
|
||||||
|
try {
|
||||||
|
const getParameter = WebGLRenderingContext.prototype.getParameter
|
||||||
|
WebGLRenderingContext.prototype.getParameter = function (parameter) {
|
||||||
|
// Randomize UNMASKED_VENDOR_WEBGL and UNMASKED_RENDERER_WEBGL
|
||||||
|
if (parameter === 37445) { // UNMASKED_VENDOR_WEBGL
|
||||||
|
return 'Intel Inc.'
|
||||||
|
}
|
||||||
|
if (parameter === 37446) { // UNMASKED_RENDERER_WEBGL
|
||||||
|
return 'Intel Iris OpenGL Engine'
|
||||||
|
}
|
||||||
|
return getParameter.apply(this, [parameter])
|
||||||
|
}
|
||||||
|
} catch { /* WebGL override may fail */ }
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// ANTI-DETECTION LAYER 3: Permissions API masking
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// CRITICAL: Mask permissions query (bots have different permissions)
|
||||||
|
try {
|
||||||
|
const originalQuery = navigator.permissions.query
|
||||||
|
// @ts-ignore
|
||||||
|
navigator.permissions.query = (parameters) => {
|
||||||
|
// Always return 'prompt' for notifications (human-like)
|
||||||
|
if (parameters.name === 'notifications') {
|
||||||
|
return Promise.resolve({ state: 'prompt', onchange: null })
|
||||||
|
}
|
||||||
|
return originalQuery(parameters)
|
||||||
|
}
|
||||||
|
} catch { /* Permissions API may not be available */ }
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// ANTI-DETECTION LAYER 4: Plugin/MIME type consistency
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// CRITICAL: Add realistic plugins (headless browsers have none)
|
||||||
|
try {
|
||||||
|
Object.defineProperty(navigator, 'plugins', {
|
||||||
|
get: () => [
|
||||||
|
{
|
||||||
|
name: 'PDF Viewer',
|
||||||
|
description: 'Portable Document Format',
|
||||||
|
filename: 'internal-pdf-viewer',
|
||||||
|
length: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Chrome PDF Viewer',
|
||||||
|
description: 'Portable Document Format',
|
||||||
|
filename: 'internal-pdf-viewer',
|
||||||
|
length: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
} catch { /* Plugins may be frozen */ }
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// Standard styling (non-detection related)
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
try {
|
try {
|
||||||
const style = document.createElement('style')
|
const style = document.createElement('style')
|
||||||
style.id = '__mrs_fit_style'
|
style.id = '__mrs_fit_style'
|
||||||
|
|||||||
@@ -472,6 +472,17 @@ export async function loadSessionData(sessionPath: string, email: string, isMobi
|
|||||||
if (shouldLoad && fs.existsSync(fingerprintFile)) {
|
if (shouldLoad && fs.existsSync(fingerprintFile)) {
|
||||||
const fingerprintData = await fs.promises.readFile(fingerprintFile, 'utf-8')
|
const fingerprintData = await fs.promises.readFile(fingerprintFile, 'utf-8')
|
||||||
fingerprint = JSON.parse(fingerprintData)
|
fingerprint = JSON.parse(fingerprintData)
|
||||||
|
|
||||||
|
// CRITICAL: Validate fingerprint age (regenerate if too old)
|
||||||
|
// Old fingerprints become suspicious as browser versions update
|
||||||
|
const fingerprintStat = await fs.promises.stat(fingerprintFile)
|
||||||
|
const ageInDays = (Date.now() - fingerprintStat.mtimeMs) / (1000 * 60 * 60 * 24)
|
||||||
|
|
||||||
|
// SECURITY: Regenerate fingerprint if older than 30 days
|
||||||
|
if (ageInDays > 30) {
|
||||||
|
// Mark as undefined to trigger regeneration
|
||||||
|
fingerprint = undefined as any
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
212
src/util/validation/FingerprintValidator.ts
Normal file
212
src/util/validation/FingerprintValidator.ts
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
/**
|
||||||
|
* Fingerprint Consistency Validator
|
||||||
|
*
|
||||||
|
* CRITICAL: Microsoft detects automation by checking for inconsistencies between:
|
||||||
|
* - Timezone (browser reported vs. IP geolocation)
|
||||||
|
* - Locale (browser language vs. IP country)
|
||||||
|
* - Screen resolution (realistic vs. bot patterns)
|
||||||
|
* - WebGL renderer (consistency check)
|
||||||
|
*
|
||||||
|
* This validator warns about potential detection risks BEFORE running automation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { BrowserFingerprintWithHeaders } from 'fingerprint-generator'
|
||||||
|
import type { Config } from '../../interface/Config'
|
||||||
|
import { log } from '../notifications/Logger'
|
||||||
|
|
||||||
|
export interface FingerprintValidationResult {
|
||||||
|
valid: boolean
|
||||||
|
warnings: string[]
|
||||||
|
criticalIssues: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate fingerprint consistency to minimize detection risk
|
||||||
|
* @param fingerprint Browser fingerprint data
|
||||||
|
* @param config Bot configuration
|
||||||
|
* @returns Validation result with warnings/errors
|
||||||
|
*/
|
||||||
|
export function validateFingerprintConsistency(
|
||||||
|
fingerprint: BrowserFingerprintWithHeaders,
|
||||||
|
config: Config
|
||||||
|
): FingerprintValidationResult {
|
||||||
|
const warnings: string[] = []
|
||||||
|
const criticalIssues: string[] = []
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// VALIDATION 1: Timezone consistency
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
try {
|
||||||
|
const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||||
|
const fingerprintTimezone = fingerprint.fingerprint.navigator?.userAgentData?.platform
|
||||||
|
|
||||||
|
// CRITICAL: Check if timezone makes sense for the platform
|
||||||
|
if (fingerprintTimezone === 'Windows') {
|
||||||
|
// Windows users rarely use non-UTC timezones
|
||||||
|
if (browserTimezone && !browserTimezone.includes('UTC') && !browserTimezone.includes('America') && !browserTimezone.includes('Europe')) {
|
||||||
|
warnings.push(`Timezone '${browserTimezone}' is unusual for Windows platform (${fingerprintTimezone})`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fingerprintTimezone === 'Android') {
|
||||||
|
// Mobile users should have timezone matching their location
|
||||||
|
// If using proxy, timezone may not match IP location (detection risk)
|
||||||
|
warnings.push('Mobile timezone consistency cannot be validated without IP geolocation data')
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Timezone validation failed (non-critical)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// VALIDATION 2: Screen resolution realism
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
try {
|
||||||
|
const screen = fingerprint.fingerprint.screen
|
||||||
|
if (screen) {
|
||||||
|
const { width, height, availWidth, availHeight } = screen
|
||||||
|
|
||||||
|
// CRITICAL: Check for unrealistic screen dimensions
|
||||||
|
if (width < 800 && height < 600) {
|
||||||
|
criticalIssues.push(`Screen size too small: ${width}x${height} (likely bot pattern)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width > 7680 || height > 4320) {
|
||||||
|
warnings.push(`Screen size unusually large: ${width}x${height} (8K+)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRITICAL: Check for exact match between screen and available size (bot pattern)
|
||||||
|
if (width === availWidth && height === availHeight) {
|
||||||
|
warnings.push('Screen size exactly matches available size (possible bot pattern - no taskbar/menubar)')
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRITICAL: Check devicePixelRatio realism
|
||||||
|
const dpr = screen.devicePixelRatio
|
||||||
|
if (dpr && (dpr < 0.5 || dpr > 5)) {
|
||||||
|
warnings.push(`Device pixel ratio unusual: ${dpr} (expected 0.5-5)`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Screen validation failed (non-critical)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// VALIDATION 3: User agent consistency
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
try {
|
||||||
|
const ua = fingerprint.fingerprint.navigator.userAgent
|
||||||
|
const uaPlatform = fingerprint.fingerprint.navigator?.userAgentData?.platform
|
||||||
|
|
||||||
|
// CRITICAL: Check for mismatched platform indicators
|
||||||
|
if (ua.includes('Windows') && uaPlatform !== 'Windows') {
|
||||||
|
criticalIssues.push(`User agent platform mismatch: UA says Windows, platform says ${uaPlatform}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ua.includes('Android') && uaPlatform !== 'Android') {
|
||||||
|
criticalIssues.push(`User agent platform mismatch: UA says Android, platform says ${uaPlatform}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRITICAL: Check for outdated browser versions (bot indicator)
|
||||||
|
const chromeMatch = ua.match(/Chrome\/(\d+)/)
|
||||||
|
if (chromeMatch) {
|
||||||
|
const chromeVersion = parseInt(chromeMatch[1] || '0')
|
||||||
|
const currentYear = new Date().getFullYear()
|
||||||
|
const currentMonth = new Date().getMonth() + 1
|
||||||
|
|
||||||
|
// Chrome releases ~6 versions per year (every 2 months)
|
||||||
|
// Rough estimation: version 100 in 2022, +6 per year
|
||||||
|
const expectedMinVersion = 100 + ((currentYear - 2022) * 6) + Math.floor(currentMonth / 2)
|
||||||
|
|
||||||
|
if (chromeVersion < expectedMinVersion - 20) {
|
||||||
|
warnings.push(`Chrome version ${chromeVersion} is outdated (expected ${expectedMinVersion - 20}+)`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// UA validation failed (non-critical)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// VALIDATION 4: Header consistency
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
try {
|
||||||
|
const headers = fingerprint.headers
|
||||||
|
|
||||||
|
// CRITICAL: Check for missing critical headers (bot indicator)
|
||||||
|
const requiredHeaders = ['user-agent', 'accept', 'accept-language', 'sec-ch-ua']
|
||||||
|
for (const header of requiredHeaders) {
|
||||||
|
if (!headers[header]) {
|
||||||
|
criticalIssues.push(`Missing critical header: ${header}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRITICAL: Check sec-ch-ua consistency with user agent
|
||||||
|
if (headers['sec-ch-ua'] && headers['user-agent']) {
|
||||||
|
const secChUa = headers['sec-ch-ua']
|
||||||
|
const ua = headers['user-agent']
|
||||||
|
|
||||||
|
// Extract Edge version from sec-ch-ua
|
||||||
|
const edgeMatch = secChUa.match(/"Microsoft Edge";v="(\d+)"/)
|
||||||
|
const uaEdgeMatch = ua.match(/Edg[A]?\/(\d+)/)
|
||||||
|
|
||||||
|
if (edgeMatch && uaEdgeMatch) {
|
||||||
|
const secChVersion = edgeMatch[1]
|
||||||
|
const uaVersion = uaEdgeMatch[1]
|
||||||
|
|
||||||
|
if (secChVersion !== uaVersion) {
|
||||||
|
criticalIssues.push(`Edge version mismatch: sec-ch-ua=${secChVersion}, user-agent=${uaVersion}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Header validation failed (non-critical)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// VALIDATION 5: Fingerprint persistence check
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
if (!config.saveFingerprint?.desktop && !config.saveFingerprint?.mobile) {
|
||||||
|
warnings.push('Fingerprint persistence disabled - each run generates new fingerprint (high detection risk)')
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// Final verdict
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
const valid = criticalIssues.length === 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid,
|
||||||
|
warnings,
|
||||||
|
criticalIssues
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log fingerprint validation results
|
||||||
|
* @param result Validation result
|
||||||
|
* @param email Account email for context
|
||||||
|
*/
|
||||||
|
export function logFingerprintValidation(result: FingerprintValidationResult, email: string): void {
|
||||||
|
if (result.criticalIssues.length > 0) {
|
||||||
|
log('main', 'FINGERPRINT', `⚠️ CRITICAL ISSUES detected for ${email}:`, 'error')
|
||||||
|
result.criticalIssues.forEach(issue => {
|
||||||
|
log('main', 'FINGERPRINT', ` ❌ ${issue}`, 'error')
|
||||||
|
})
|
||||||
|
log('main', 'FINGERPRINT', '🚨 Account may be flagged as bot - high ban risk!', 'error')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.warnings.length > 0 && result.criticalIssues.length === 0) {
|
||||||
|
log('main', 'FINGERPRINT', `⚠️ Warnings for ${email}:`, 'warn')
|
||||||
|
result.warnings.forEach(warning => {
|
||||||
|
log('main', 'FINGERPRINT', ` ⚠️ ${warning}`, 'warn')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.valid && result.warnings.length === 0) {
|
||||||
|
log('main', 'FINGERPRINT', `✅ Fingerprint validation passed for ${email}`, 'log')
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user