Add Net USD Value display to dashboard
- Updated AccountBalance interface to include netUsdValue and unrealizedPnl - Enhanced getAccountBalance() to calculate Net USD Value (collateral + unrealized PnL) - Added Net USD Value calculation from all position unrealized PnL - Updated Dashboard.tsx to display Net USD Value as primary metric - Added new stats card with emerald styling for Net USD Value - Reorganized stats grid to 6 columns to accommodate new metric - Net USD Value = Total Collateral + Total Unrealized PnL from all positions
This commit is contained in:
@@ -17,7 +17,8 @@ export default function Dashboard() {
|
|||||||
dailyPnL: 0,
|
dailyPnL: 0,
|
||||||
winRate: 0,
|
winRate: 0,
|
||||||
totalTrades: 0,
|
totalTrades: 0,
|
||||||
accountValue: 0
|
accountValue: 0,
|
||||||
|
netUsdValue: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -48,7 +49,8 @@ export default function Dashboard() {
|
|||||||
const balanceData = await balanceRes.json()
|
const balanceData = await balanceRes.json()
|
||||||
setStats(prev => ({
|
setStats(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
accountValue: balanceData.accountValue || 0
|
accountValue: balanceData.accountValue || 0,
|
||||||
|
netUsdValue: balanceData.netUsdValue || 0
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -62,7 +64,8 @@ export default function Dashboard() {
|
|||||||
dailyPnL: 0,
|
dailyPnL: 0,
|
||||||
winRate: 0,
|
winRate: 0,
|
||||||
totalTrades: 0,
|
totalTrades: 0,
|
||||||
accountValue: 0
|
accountValue: 0,
|
||||||
|
netUsdValue: 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -74,7 +77,8 @@ export default function Dashboard() {
|
|||||||
dailyPnL: 0,
|
dailyPnL: 0,
|
||||||
winRate: 0,
|
winRate: 0,
|
||||||
totalTrades: 0,
|
totalTrades: 0,
|
||||||
accountValue: 0
|
accountValue: 0,
|
||||||
|
netUsdValue: 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -86,7 +90,8 @@ export default function Dashboard() {
|
|||||||
dailyPnL: 0,
|
dailyPnL: 0,
|
||||||
winRate: 0,
|
winRate: 0,
|
||||||
totalTrades: 0,
|
totalTrades: 0,
|
||||||
accountValue: 0
|
accountValue: 0,
|
||||||
|
netUsdValue: 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
@@ -97,7 +102,21 @@ export default function Dashboard() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{/* Stats Cards */}
|
{/* Stats Cards */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-6 gap-6">
|
||||||
|
<div className="card card-gradient">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-gray-400 text-sm font-medium">Net USD Value</p>
|
||||||
|
<p className="text-2xl font-bold text-emerald-400">
|
||||||
|
${stats.netUsdValue.toFixed(2)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-12 h-12 bg-emerald-500/20 rounded-full flex items-center justify-center">
|
||||||
|
<span className="text-emerald-400 text-xl">💎</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="card card-gradient">
|
<div className="card card-gradient">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ export interface AccountBalance {
|
|||||||
accountValue: number
|
accountValue: number
|
||||||
leverage: number
|
leverage: number
|
||||||
availableBalance: number
|
availableBalance: number
|
||||||
|
netUsdValue: number
|
||||||
|
unrealizedPnl: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TradeHistory {
|
export interface TradeHistory {
|
||||||
@@ -181,8 +183,11 @@ export class DriftTradingService {
|
|||||||
async getAccountBalance(): Promise<AccountBalance> {
|
async getAccountBalance(): Promise<AccountBalance> {
|
||||||
try {
|
try {
|
||||||
if (this.isInitialized && this.driftClient) {
|
if (this.isInitialized && this.driftClient) {
|
||||||
// Try to use SDK without subscription
|
// Subscribe to user account to access balance data
|
||||||
try {
|
try {
|
||||||
|
console.log('🔍 Subscribing to user account for balance...')
|
||||||
|
await this.driftClient.subscribe()
|
||||||
|
|
||||||
const user = this.driftClient.getUser()
|
const user = this.driftClient.getUser()
|
||||||
|
|
||||||
// Get account equity and collateral information using proper SDK methods
|
// Get account equity and collateral information using proper SDK methods
|
||||||
@@ -213,17 +218,67 @@ export class DriftTradingService {
|
|||||||
const leverage = marginRequirement > 0 ? totalCollateral / marginRequirement : 1
|
const leverage = marginRequirement > 0 ? totalCollateral / marginRequirement : 1
|
||||||
const availableBalance = freeCollateral
|
const availableBalance = freeCollateral
|
||||||
|
|
||||||
|
// Calculate unrealized PnL from all positions
|
||||||
|
let totalUnrealizedPnl = 0
|
||||||
|
try {
|
||||||
|
// Get all perp positions to calculate total unrealized PnL
|
||||||
|
const mainMarkets = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] // Check more markets for PnL
|
||||||
|
|
||||||
|
for (const marketIndex of mainMarkets) {
|
||||||
|
try {
|
||||||
|
const position = user.getPerpPosition(marketIndex)
|
||||||
|
if (!position || position.baseAssetAmount.isZero()) continue
|
||||||
|
|
||||||
|
// Calculate unrealized PnL manually
|
||||||
|
const marketData = this.driftClient.getPerpMarketAccount(marketIndex)
|
||||||
|
const markPrice = convertToNumber(marketData?.amm.lastMarkPriceTwap || new BN(0), PRICE_PRECISION)
|
||||||
|
|
||||||
|
const entryPrice = convertToNumber(position.quoteEntryAmount.abs(), PRICE_PRECISION) /
|
||||||
|
convertToNumber(position.baseAssetAmount.abs(), BASE_PRECISION)
|
||||||
|
const size = convertToNumber(position.baseAssetAmount.abs(), BASE_PRECISION)
|
||||||
|
const isLong = position.baseAssetAmount.gt(new BN(0))
|
||||||
|
|
||||||
|
const unrealizedPnl = isLong ?
|
||||||
|
(markPrice - entryPrice) * size :
|
||||||
|
(entryPrice - markPrice) * size
|
||||||
|
|
||||||
|
totalUnrealizedPnl += unrealizedPnl
|
||||||
|
} catch (e) {
|
||||||
|
// Skip markets that don't exist
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not calculate unrealized PnL:', e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Net USD Value = Total Collateral + Unrealized PnL
|
||||||
|
const netUsdValue = totalCollateral + totalUnrealizedPnl
|
||||||
|
|
||||||
|
console.log(`💰 Account balance: $${accountValue.toFixed(2)}, Net USD: $${netUsdValue.toFixed(2)}, PnL: $${totalUnrealizedPnl.toFixed(2)}`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalCollateral,
|
totalCollateral,
|
||||||
freeCollateral,
|
freeCollateral,
|
||||||
marginRequirement,
|
marginRequirement,
|
||||||
accountValue,
|
accountValue,
|
||||||
leverage,
|
leverage,
|
||||||
availableBalance
|
availableBalance,
|
||||||
|
netUsdValue,
|
||||||
|
unrealizedPnl: totalUnrealizedPnl
|
||||||
}
|
}
|
||||||
} catch (sdkError: any) {
|
} catch (sdkError: any) {
|
||||||
console.log('⚠️ SDK method failed, using fallback:', sdkError.message)
|
console.log('⚠️ SDK balance method failed, using fallback:', sdkError.message)
|
||||||
// Fall through to fallback method
|
// Fall through to fallback method
|
||||||
|
} finally {
|
||||||
|
// Always unsubscribe to clean up
|
||||||
|
if (this.driftClient) {
|
||||||
|
try {
|
||||||
|
await this.driftClient.unsubscribe()
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore unsubscribe errors
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +292,9 @@ export class DriftTradingService {
|
|||||||
marginRequirement: 0,
|
marginRequirement: 0,
|
||||||
accountValue: balance / 1e9, // SOL balance
|
accountValue: balance / 1e9, // SOL balance
|
||||||
leverage: 0,
|
leverage: 0,
|
||||||
availableBalance: 0
|
availableBalance: 0,
|
||||||
|
netUsdValue: balance / 1e9, // Use SOL balance as fallback
|
||||||
|
unrealizedPnl: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -281,8 +338,11 @@ export class DriftTradingService {
|
|||||||
async getPositions(): Promise<Position[]> {
|
async getPositions(): Promise<Position[]> {
|
||||||
try {
|
try {
|
||||||
if (this.isInitialized && this.driftClient) {
|
if (this.isInitialized && this.driftClient) {
|
||||||
// Try to use SDK without subscription
|
// Subscribe to user account to access positions
|
||||||
try {
|
try {
|
||||||
|
console.log('🔍 Subscribing to user account for positions...')
|
||||||
|
await this.driftClient.subscribe()
|
||||||
|
|
||||||
const user = this.driftClient.getUser()
|
const user = this.driftClient.getUser()
|
||||||
|
|
||||||
// Get all available markets
|
// Get all available markets
|
||||||
@@ -296,7 +356,7 @@ export class DriftTradingService {
|
|||||||
const p = user.getPerpPosition(marketIndex)
|
const p = user.getPerpPosition(marketIndex)
|
||||||
if (!p || p.baseAssetAmount.isZero()) continue
|
if (!p || p.baseAssetAmount.isZero()) continue
|
||||||
|
|
||||||
// Get market price without subscription
|
// Get market price
|
||||||
const marketData = this.driftClient.getPerpMarketAccount(marketIndex)
|
const marketData = this.driftClient.getPerpMarketAccount(marketIndex)
|
||||||
const markPrice = convertToNumber(marketData?.amm.lastMarkPriceTwap || new BN(0), PRICE_PRECISION)
|
const markPrice = convertToNumber(marketData?.amm.lastMarkPriceTwap || new BN(0), PRICE_PRECISION)
|
||||||
|
|
||||||
@@ -319,16 +379,29 @@ export class DriftTradingService {
|
|||||||
marketIndex,
|
marketIndex,
|
||||||
marketType: 'PERP'
|
marketType: 'PERP'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
console.log(`✅ Found position: ${this.getSymbolFromMarketIndex(marketIndex)} ${isLong ? 'LONG' : 'SHORT'} ${size}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Skip markets that don't exist or have errors
|
// Skip markets that don't exist or have errors
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`📊 Found ${positions.length} total positions`)
|
||||||
return positions
|
return positions
|
||||||
|
|
||||||
} catch (sdkError: any) {
|
} catch (sdkError: any) {
|
||||||
console.log('⚠️ SDK positions method failed, using fallback:', sdkError.message)
|
console.log('⚠️ SDK positions method failed, using fallback:', sdkError.message)
|
||||||
// Fall through to fallback method
|
// Fall through to fallback method
|
||||||
|
} finally {
|
||||||
|
// Always unsubscribe to clean up
|
||||||
|
if (this.driftClient) {
|
||||||
|
try {
|
||||||
|
await this.driftClient.unsubscribe()
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore unsubscribe errors
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export class TradingViewAutomation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.browser) {
|
if (this.browser) {
|
||||||
console.log('✅ Browser already initialized and connected')
|
console.log('SUCCESS: Browser already initialized and connected')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ export class TradingViewAutomation {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Failed to launch browser:', error)
|
console.error('ERROR: Failed to launch browser:', error)
|
||||||
// Cleanup any partial state
|
// Cleanup any partial state
|
||||||
await this.forceCleanup()
|
await this.forceCleanup()
|
||||||
throw new Error(`Failed to launch browser: ${error}`)
|
throw new Error(`Failed to launch browser: ${error}`)
|
||||||
@@ -300,7 +300,7 @@ export class TradingViewAutomation {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('✅ Browser and session initialized successfully')
|
console.log('SUCCESS: Browser and session initialized successfully')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -310,7 +310,7 @@ export class TradingViewAutomation {
|
|||||||
if (!this.page) return false
|
if (!this.page) return false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('🔍 Checking login status...')
|
console.log('CHECKING: Checking login status...')
|
||||||
|
|
||||||
// Navigate to TradingView if not already there
|
// Navigate to TradingView if not already there
|
||||||
const currentUrl = await this.page.url()
|
const currentUrl = await this.page.url()
|
||||||
@@ -332,7 +332,7 @@ export class TradingViewAutomation {
|
|||||||
await this.takeDebugScreenshot('login_status_check')
|
await this.takeDebugScreenshot('login_status_check')
|
||||||
|
|
||||||
// Enhanced login detection with multiple strategies
|
// Enhanced login detection with multiple strategies
|
||||||
console.log('🔍 Strategy 1: Checking for user account indicators...')
|
console.log('CHECKING: Strategy 1: Checking for user account indicators...')
|
||||||
|
|
||||||
// Strategy 1: Look for user account elements (more comprehensive)
|
// Strategy 1: Look for user account elements (more comprehensive)
|
||||||
const userAccountSelectors = [
|
const userAccountSelectors = [
|
||||||
@@ -366,7 +366,7 @@ export class TradingViewAutomation {
|
|||||||
for (const selector of userAccountSelectors) {
|
for (const selector of userAccountSelectors) {
|
||||||
try {
|
try {
|
||||||
if (await this.page.locator(selector).isVisible({ timeout: 1500 })) {
|
if (await this.page.locator(selector).isVisible({ timeout: 1500 })) {
|
||||||
console.log(`✅ Found user account element: ${selector}`)
|
console.log(`SUCCESS: Found user account element: ${selector}`)
|
||||||
foundUserElement = true
|
foundUserElement = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -376,7 +376,7 @@ export class TradingViewAutomation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Strategy 2: Check for sign-in/anonymous indicators (should NOT be present if logged in)
|
// Strategy 2: Check for sign-in/anonymous indicators (should NOT be present if logged in)
|
||||||
console.log('🔍 Strategy 2: Checking for anonymous/sign-in indicators...')
|
console.log('CHECKING: Strategy 2: Checking for anonymous/sign-in indicators...')
|
||||||
|
|
||||||
const anonymousSelectors = [
|
const anonymousSelectors = [
|
||||||
// Sign in buttons/links
|
// Sign in buttons/links
|
||||||
@@ -402,7 +402,7 @@ export class TradingViewAutomation {
|
|||||||
for (const selector of anonymousSelectors) {
|
for (const selector of anonymousSelectors) {
|
||||||
try {
|
try {
|
||||||
if (await this.page.locator(selector).isVisible({ timeout: 1500 })) {
|
if (await this.page.locator(selector).isVisible({ timeout: 1500 })) {
|
||||||
console.log(`❌ Found anonymous indicator: ${selector} - not logged in`)
|
console.log(`ERROR: Found anonymous indicator: ${selector} - not logged in`)
|
||||||
foundAnonymousElement = true
|
foundAnonymousElement = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -412,7 +412,7 @@ export class TradingViewAutomation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Strategy 3: Check page URL patterns for authentication
|
// Strategy 3: Check page URL patterns for authentication
|
||||||
console.log('🔍 Strategy 3: Checking URL patterns...')
|
console.log('CHECKING: Strategy 3: Checking URL patterns...')
|
||||||
|
|
||||||
const url = await this.page.url()
|
const url = await this.page.url()
|
||||||
const isOnLoginPage = url.includes('/accounts/signin') ||
|
const isOnLoginPage = url.includes('/accounts/signin') ||
|
||||||
@@ -420,13 +420,13 @@ export class TradingViewAutomation {
|
|||||||
url.includes('/login')
|
url.includes('/login')
|
||||||
|
|
||||||
if (isOnLoginPage) {
|
if (isOnLoginPage) {
|
||||||
console.log(`❌ Currently on login page: ${url}`)
|
console.log(`ERROR: Currently on login page: ${url}`)
|
||||||
this.isAuthenticated = false
|
this.isAuthenticated = false
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy 4: Check for authentication-specific cookies
|
// Strategy 4: Check for authentication-specific cookies
|
||||||
console.log('🔍 Strategy 4: Checking authentication cookies...')
|
console.log('CHECKING: Strategy 4: Checking authentication cookies...')
|
||||||
|
|
||||||
let hasAuthCookies = false
|
let hasAuthCookies = false
|
||||||
if (this.context) {
|
if (this.context) {
|
||||||
@@ -450,11 +450,11 @@ export class TradingViewAutomation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`📊 Total cookies: ${cookies.length}, Auth cookies found: ${hasAuthCookies}`)
|
console.log(`DATA: Total cookies: ${cookies.length}, Auth cookies found: ${hasAuthCookies}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy 5: Try to detect personal elements by checking page content
|
// Strategy 5: Try to detect personal elements by checking page content
|
||||||
console.log('🔍 Strategy 5: Checking for personal content...')
|
console.log('CHECKING: Strategy 5: Checking for personal content...')
|
||||||
|
|
||||||
let hasPersonalContent = false
|
let hasPersonalContent = false
|
||||||
try {
|
try {
|
||||||
@@ -476,7 +476,7 @@ export class TradingViewAutomation {
|
|||||||
for (const selector of personalContentSelectors) {
|
for (const selector of personalContentSelectors) {
|
||||||
try {
|
try {
|
||||||
if (await this.page.locator(selector).isVisible({ timeout: 1000 })) {
|
if (await this.page.locator(selector).isVisible({ timeout: 1000 })) {
|
||||||
console.log(`✅ Found personal content: ${selector}`)
|
console.log(`SUCCESS: Found personal content: ${selector}`)
|
||||||
hasPersonalContent = true
|
hasPersonalContent = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -491,16 +491,16 @@ export class TradingViewAutomation {
|
|||||||
pageText.includes('Portfolio') ||
|
pageText.includes('Portfolio') ||
|
||||||
pageText.includes('Alerts') ||
|
pageText.includes('Alerts') ||
|
||||||
pageText.includes('Account')) {
|
pageText.includes('Account')) {
|
||||||
console.log('✅ Found account-specific text content')
|
console.log('SUCCESS: Found account-specific text content')
|
||||||
hasPersonalContent = true
|
hasPersonalContent = true
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('⚠️ Error checking personal content:', e)
|
console.log('WARNING: Error checking personal content:', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final decision logic
|
// Final decision logic
|
||||||
console.log('📊 Login detection summary:')
|
console.log('DATA: Login detection summary:')
|
||||||
console.log(` User elements found: ${foundUserElement}`)
|
console.log(` User elements found: ${foundUserElement}`)
|
||||||
console.log(` Anonymous elements found: ${foundAnonymousElement}`)
|
console.log(` Anonymous elements found: ${foundAnonymousElement}`)
|
||||||
console.log(` On login page: ${isOnLoginPage}`)
|
console.log(` On login page: ${isOnLoginPage}`)
|
||||||
@@ -513,17 +513,17 @@ export class TradingViewAutomation {
|
|||||||
!isOnLoginPage
|
!isOnLoginPage
|
||||||
|
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
console.log('✅ User appears to be logged in')
|
console.log('SUCCESS: User appears to be logged in')
|
||||||
this.isAuthenticated = true
|
this.isAuthenticated = true
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
console.log('❌ User appears to be NOT logged in')
|
console.log('ERROR: User appears to be NOT logged in')
|
||||||
this.isAuthenticated = false
|
this.isAuthenticated = false
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error checking login status:', error)
|
console.error('ERROR: Error checking login status:', error)
|
||||||
await this.takeDebugScreenshot('login_status_error')
|
await this.takeDebugScreenshot('login_status_error')
|
||||||
this.isAuthenticated = false
|
this.isAuthenticated = false
|
||||||
return false
|
return false
|
||||||
@@ -545,7 +545,7 @@ export class TradingViewAutomation {
|
|||||||
// Check if already logged in with enhanced detection
|
// Check if already logged in with enhanced detection
|
||||||
const loggedIn = await this.checkLoginStatus()
|
const loggedIn = await this.checkLoginStatus()
|
||||||
if (loggedIn) {
|
if (loggedIn) {
|
||||||
console.log('✅ Already logged in, skipping login steps')
|
console.log('SUCCESS: Already logged in, skipping login steps')
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,7 +557,7 @@ export class TradingViewAutomation {
|
|||||||
await this.context.clearCookies()
|
await this.context.clearCookies()
|
||||||
console.log('🧹 Cleared existing cookies for clean login')
|
console.log('🧹 Cleared existing cookies for clean login')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('⚠️ Could not clear cookies:', e)
|
console.log('WARNING: Could not clear cookies:', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -589,7 +589,7 @@ export class TradingViewAutomation {
|
|||||||
break
|
break
|
||||||
} else if (url === 'https://www.tradingview.com/') {
|
} else if (url === 'https://www.tradingview.com/') {
|
||||||
// Try to find and click sign in button
|
// Try to find and click sign in button
|
||||||
console.log('🔍 Looking for Sign In button on main page...')
|
console.log('CHECKING: Looking for Sign In button on main page...')
|
||||||
|
|
||||||
const signInSelectors = [
|
const signInSelectors = [
|
||||||
'a[href*="signin"]:visible',
|
'a[href*="signin"]:visible',
|
||||||
@@ -602,24 +602,24 @@ export class TradingViewAutomation {
|
|||||||
|
|
||||||
for (const selector of signInSelectors) {
|
for (const selector of signInSelectors) {
|
||||||
try {
|
try {
|
||||||
console.log(`🎯 Trying sign in selector: ${selector}`)
|
console.log(`TARGET: Trying sign in selector: ${selector}`)
|
||||||
const element = this.page.locator(selector).first()
|
const element = this.page.locator(selector).first()
|
||||||
if (await element.isVisible({ timeout: 3000 })) {
|
if (await element.isVisible({ timeout: 3000 })) {
|
||||||
await element.click()
|
await element.click()
|
||||||
console.log(`✅ Clicked sign in button: ${selector}`)
|
console.log(`SUCCESS: Clicked sign in button: ${selector}`)
|
||||||
|
|
||||||
// Wait for navigation to login page
|
// Wait for navigation to login page
|
||||||
await this.page.waitForTimeout(3000)
|
await this.page.waitForTimeout(3000)
|
||||||
|
|
||||||
const newUrl = await this.page.url()
|
const newUrl = await this.page.url()
|
||||||
if (newUrl.includes('signin') || newUrl.includes('login')) {
|
if (newUrl.includes('signin') || newUrl.includes('login')) {
|
||||||
console.log('✅ Successfully navigated to login page')
|
console.log('SUCCESS: Successfully navigated to login page')
|
||||||
loginPageLoaded = true
|
loginPageLoaded = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`❌ Sign in selector failed: ${selector}`)
|
console.log(`ERROR: Sign in selector failed: ${selector}`)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -628,7 +628,7 @@ export class TradingViewAutomation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`❌ Failed to load ${url}:`, e)
|
console.log(`ERROR: Failed to load ${url}:`, e)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -645,7 +645,7 @@ export class TradingViewAutomation {
|
|||||||
await this.page.waitForTimeout(5000)
|
await this.page.waitForTimeout(5000)
|
||||||
|
|
||||||
// CRITICAL: Look for and click "Email" button if present (TradingView uses this pattern)
|
// CRITICAL: Look for and click "Email" button if present (TradingView uses this pattern)
|
||||||
console.log('🔍 Looking for Email login option...')
|
console.log('CHECKING: Looking for Email login option...')
|
||||||
|
|
||||||
// First try Playwright locator approach
|
// First try Playwright locator approach
|
||||||
const emailTriggers = [
|
const emailTriggers = [
|
||||||
@@ -663,9 +663,9 @@ export class TradingViewAutomation {
|
|||||||
try {
|
try {
|
||||||
const element = this.page.locator(trigger).first()
|
const element = this.page.locator(trigger).first()
|
||||||
if (await element.isVisible({ timeout: 2000 })) {
|
if (await element.isVisible({ timeout: 2000 })) {
|
||||||
console.log(`🎯 Found email trigger: ${trigger}`)
|
console.log(`TARGET: Found email trigger: ${trigger}`)
|
||||||
await element.click()
|
await element.click()
|
||||||
console.log('✅ Clicked email trigger')
|
console.log('SUCCESS: Clicked email trigger')
|
||||||
|
|
||||||
// Wait for email form to appear
|
// Wait for email form to appear
|
||||||
await this.page.waitForTimeout(3000)
|
await this.page.waitForTimeout(3000)
|
||||||
@@ -686,7 +686,7 @@ export class TradingViewAutomation {
|
|||||||
|
|
||||||
// Get all buttons and check their text content
|
// Get all buttons and check their text content
|
||||||
const buttons = await this.page.locator('button').all()
|
const buttons = await this.page.locator('button').all()
|
||||||
console.log(`🔍 Found ${buttons.length} buttons to check`)
|
console.log(`CHECKING: Found ${buttons.length} buttons to check`)
|
||||||
|
|
||||||
for (let i = 0; i < buttons.length; i++) {
|
for (let i = 0; i < buttons.length; i++) {
|
||||||
try {
|
try {
|
||||||
@@ -695,14 +695,14 @@ export class TradingViewAutomation {
|
|||||||
const text = await button.textContent() || ''
|
const text = await button.textContent() || ''
|
||||||
const trimmedText = text.trim().toLowerCase()
|
const trimmedText = text.trim().toLowerCase()
|
||||||
|
|
||||||
console.log(`📝
|
console.log(`INFO:
|
||||||
|
|
||||||
if (trimmedText.includes('email') ||
|
if (trimmedText.includes('email') ||
|
||||||
trimmedText.includes('continue with email') ||
|
trimmedText.includes('continue with email') ||
|
||||||
trimmedText.includes('sign in with email')) {
|
trimmedText.includes('sign in with email')) {
|
||||||
console.log(`🎯 Found email button: "${trimmedText}"`)
|
console.log('Found email button: ' + trimmedText)
|
||||||
await button.click()
|
await button.click()
|
||||||
console.log('✅ Clicked email button')
|
console.log('SUCCESS: Clicked email button')
|
||||||
|
|
||||||
// Wait for email form to appear
|
// Wait for email form to appear
|
||||||
await this.page.waitForTimeout(3000)
|
await this.page.waitForTimeout(3000)
|
||||||
@@ -711,12 +711,12 @@ export class TradingViewAutomation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`⚠️ Error checking button ${i + 1}:`, e)
|
console.log('WARNING: Error checking button ' + (i + 1) + ':', e)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('❌ Manual button search failed:', e)
|
console.log('ERROR: Manual button search failed:', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -735,7 +735,7 @@ export class TradingViewAutomation {
|
|||||||
for (const selector of emailInputSelectors) {
|
for (const selector of emailInputSelectors) {
|
||||||
try {
|
try {
|
||||||
if (await this.page.locator(selector).isVisible({ timeout: 1000 })) {
|
if (await this.page.locator(selector).isVisible({ timeout: 1000 })) {
|
||||||
console.log(`✅ Email input already visible: ${selector}`)
|
console.log(`SUCCESS: Email input already visible: ${selector}`)
|
||||||
emailFormVisible = true
|
emailFormVisible = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -760,7 +760,7 @@ export class TradingViewAutomation {
|
|||||||
return { buttons, inputs, forms }
|
return { buttons, inputs, forms }
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('🔍 Available elements:', JSON.stringify(availableElements, null, 2))
|
console.log('CHECKING: Available elements:', JSON.stringify(availableElements, null, 2))
|
||||||
throw new Error('Could not find or activate email login form')
|
throw new Error('Could not find or activate email login form')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -782,10 +782,10 @@ export class TradingViewAutomation {
|
|||||||
let emailInput = null
|
let emailInput = null
|
||||||
for (const selector of emailSelectors) {
|
for (const selector of emailSelectors) {
|
||||||
try {
|
try {
|
||||||
console.log(`🔍 Trying email selector: ${selector}`)
|
console.log(`CHECKING: Trying email selector: ${selector}`)
|
||||||
if (await this.page.locator(selector).isVisible({ timeout: 2000 })) {
|
if (await this.page.locator(selector).isVisible({ timeout: 2000 })) {
|
||||||
emailInput = selector
|
emailInput = selector
|
||||||
console.log(`✅ Found email input: ${selector}`)
|
console.log(`SUCCESS: Found email input: ${selector}`)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -798,7 +798,7 @@ export class TradingViewAutomation {
|
|||||||
console.log('🔄 Selector approach failed, trying manual input search...')
|
console.log('🔄 Selector approach failed, trying manual input search...')
|
||||||
try {
|
try {
|
||||||
const inputs = await this.page.locator('input').all()
|
const inputs = await this.page.locator('input').all()
|
||||||
console.log(`🔍 Found ${inputs.length} inputs to check`)
|
console.log(`CHECKING: Found ${inputs.length} inputs to check`)
|
||||||
|
|
||||||
for (let i = 0; i < inputs.length; i++) {
|
for (let i = 0; i < inputs.length; i++) {
|
||||||
try {
|
try {
|
||||||
@@ -808,14 +808,14 @@ export class TradingViewAutomation {
|
|||||||
const name = await input.getAttribute('name') || ''
|
const name = await input.getAttribute('name') || ''
|
||||||
const placeholder = await input.getAttribute('placeholder') || ''
|
const placeholder = await input.getAttribute('placeholder') || ''
|
||||||
|
|
||||||
console.log(`📝 Input ${i + 1}: type="${type}" name="${name}" placeholder="${placeholder}"`)
|
console.log(`INFO: Input ${i + 1}: type="${type}" name="${name}" placeholder="${placeholder}"`)
|
||||||
|
|
||||||
if (type === 'email' ||
|
if (type === 'email' ||
|
||||||
name.toLowerCase().includes('email') ||
|
name.toLowerCase().includes('email') ||
|
||||||
name.toLowerCase().includes('username') ||
|
name.toLowerCase().includes('username') ||
|
||||||
placeholder.toLowerCase().includes('email') ||
|
placeholder.toLowerCase().includes('email') ||
|
||||||
placeholder.toLowerCase().includes('username')) {
|
placeholder.toLowerCase().includes('username')) {
|
||||||
console.log(`🎯 Found email input manually: ${name || type || placeholder}`)
|
console.log(`TARGET: Found email input manually: ${name || type || placeholder}`)
|
||||||
emailInput = `input:nth-of-type(${i + 1})`
|
emailInput = `input:nth-of-type(${i + 1})`
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -825,7 +825,7 @@ export class TradingViewAutomation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('❌ Manual input search failed:', e)
|
console.log('ERROR: Manual input search failed:', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -837,7 +837,7 @@ export class TradingViewAutomation {
|
|||||||
// Fill email
|
// Fill email
|
||||||
console.log('📧 Filling email field...')
|
console.log('📧 Filling email field...')
|
||||||
await this.page.fill(emailInput, email)
|
await this.page.fill(emailInput, email)
|
||||||
console.log('✅ Email filled')
|
console.log('SUCCESS: Email filled')
|
||||||
|
|
||||||
// Find and fill password field
|
// Find and fill password field
|
||||||
console.log('🔑 Looking for password input field...')
|
console.log('🔑 Looking for password input field...')
|
||||||
@@ -853,10 +853,10 @@ export class TradingViewAutomation {
|
|||||||
let passwordInput = null
|
let passwordInput = null
|
||||||
for (const selector of passwordSelectors) {
|
for (const selector of passwordSelectors) {
|
||||||
try {
|
try {
|
||||||
console.log(`🔍 Trying password selector: ${selector}`)
|
console.log(`CHECKING: Trying password selector: ${selector}`)
|
||||||
if (await this.page.locator(selector).isVisible({ timeout: 2000 })) {
|
if (await this.page.locator(selector).isVisible({ timeout: 2000 })) {
|
||||||
passwordInput = selector
|
passwordInput = selector
|
||||||
console.log(`✅ Found password input: ${selector}`)
|
console.log(`SUCCESS: Found password input: ${selector}`)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -872,7 +872,7 @@ export class TradingViewAutomation {
|
|||||||
// Fill password
|
// Fill password
|
||||||
console.log('🔑 Filling password field...')
|
console.log('🔑 Filling password field...')
|
||||||
await this.page.fill(passwordInput, password)
|
await this.page.fill(passwordInput, password)
|
||||||
console.log('✅ Password filled')
|
console.log('SUCCESS: Password filled')
|
||||||
|
|
||||||
// Handle potential captcha
|
// Handle potential captcha
|
||||||
console.log('🤖 Checking for captcha...')
|
console.log('🤖 Checking for captcha...')
|
||||||
@@ -909,7 +909,7 @@ export class TradingViewAutomation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (captchaFound) {
|
if (captchaFound) {
|
||||||
console.log('⚠️ CAPTCHA/Robot verification detected!')
|
console.log('WARNING: CAPTCHA/Robot verification detected!')
|
||||||
console.log('🚫 This indicates TradingView has flagged this as automated behavior.')
|
console.log('🚫 This indicates TradingView has flagged this as automated behavior.')
|
||||||
console.log('<27> In a Docker environment, automated captcha solving is not feasible.')
|
console.log('<27> In a Docker environment, automated captcha solving is not feasible.')
|
||||||
|
|
||||||
@@ -917,7 +917,7 @@ export class TradingViewAutomation {
|
|||||||
await this.takeDebugScreenshot('captcha_detected')
|
await this.takeDebugScreenshot('captcha_detected')
|
||||||
|
|
||||||
// Instead of waiting, we should fail fast and suggest alternatives
|
// Instead of waiting, we should fail fast and suggest alternatives
|
||||||
console.log('❌ Cannot proceed with automated login due to captcha protection.')
|
console.log('ERROR: Cannot proceed with automated login due to captcha protection.')
|
||||||
console.log('🔧 Possible solutions:')
|
console.log('🔧 Possible solutions:')
|
||||||
console.log(' 1. Use a different IP address or VPN')
|
console.log(' 1. Use a different IP address or VPN')
|
||||||
console.log(' 2. Wait some time before retrying (rate limiting)')
|
console.log(' 2. Wait some time before retrying (rate limiting)')
|
||||||
@@ -935,7 +935,7 @@ export class TradingViewAutomation {
|
|||||||
if (captchaError.message.includes('Captcha detected')) {
|
if (captchaError.message.includes('Captcha detected')) {
|
||||||
throw captchaError
|
throw captchaError
|
||||||
}
|
}
|
||||||
console.log('⚠️ Captcha check failed:', captchaError?.message)
|
console.log('WARNING: Captcha check failed:', captchaError?.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find and click submit button
|
// Find and click submit button
|
||||||
@@ -957,10 +957,10 @@ export class TradingViewAutomation {
|
|||||||
let submitButton = null
|
let submitButton = null
|
||||||
for (const selector of submitSelectors) {
|
for (const selector of submitSelectors) {
|
||||||
try {
|
try {
|
||||||
console.log(`🔍 Trying submit selector: ${selector}`)
|
console.log(`CHECKING: Trying submit selector: ${selector}`)
|
||||||
if (await this.page.locator(selector).isVisible({ timeout: 2000 })) {
|
if (await this.page.locator(selector).isVisible({ timeout: 2000 })) {
|
||||||
submitButton = selector
|
submitButton = selector
|
||||||
console.log(`✅ Found submit button: ${selector}`)
|
console.log(`SUCCESS: Found submit button: ${selector}`)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -973,7 +973,7 @@ export class TradingViewAutomation {
|
|||||||
console.log('🔄 Selector approach failed, trying manual button search for submit...')
|
console.log('🔄 Selector approach failed, trying manual button search for submit...')
|
||||||
try {
|
try {
|
||||||
const buttons = await this.page.locator('button').all()
|
const buttons = await this.page.locator('button').all()
|
||||||
console.log(`🔍 Found ${buttons.length} buttons to check for submit`)
|
console.log(`CHECKING: Found ${buttons.length} buttons to check for submit`)
|
||||||
|
|
||||||
for (let i = 0; i < buttons.length; i++) {
|
for (let i = 0; i < buttons.length; i++) {
|
||||||
try {
|
try {
|
||||||
@@ -982,13 +982,13 @@ export class TradingViewAutomation {
|
|||||||
const text = (await button.textContent() || '').toLowerCase()
|
const text = (await button.textContent() || '').toLowerCase()
|
||||||
const type = await button.getAttribute('type') || ''
|
const type = await button.getAttribute('type') || ''
|
||||||
|
|
||||||
console.log(`📝 Submit Button ${i + 1}: "${text}" type="${type}"`)
|
console.log(`INFO: Submit Button ${i + 1}: "${text}" type="${type}"`)
|
||||||
|
|
||||||
if (type === 'submit' ||
|
if (type === 'submit' ||
|
||||||
text.includes('sign in') ||
|
text.includes('sign in') ||
|
||||||
text.includes('login') ||
|
text.includes('login') ||
|
||||||
text.includes('submit')) {
|
text.includes('submit')) {
|
||||||
console.log(`🎯 Found submit button manually: "${text}"`)
|
console.log(`TARGET: Found submit button manually: "${text}"`)
|
||||||
submitButton = `button:nth-of-type(${i + 1})`
|
submitButton = `button:nth-of-type(${i + 1})`
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -998,7 +998,7 @@ export class TradingViewAutomation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('❌ Manual submit button search failed:', e)
|
console.log('ERROR: Manual submit button search failed:', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1010,7 +1010,7 @@ export class TradingViewAutomation {
|
|||||||
// Click submit button
|
// Click submit button
|
||||||
console.log('🖱️ Clicking submit button...')
|
console.log('🖱️ Clicking submit button...')
|
||||||
await this.page.click(submitButton)
|
await this.page.click(submitButton)
|
||||||
console.log('✅ Submit button clicked')
|
console.log('SUCCESS: Submit button clicked')
|
||||||
|
|
||||||
// Wait for login to complete
|
// Wait for login to complete
|
||||||
console.log('⏳ Waiting for login to complete...')
|
console.log('⏳ Waiting for login to complete...')
|
||||||
@@ -1041,7 +1041,7 @@ export class TradingViewAutomation {
|
|||||||
).first()
|
).first()
|
||||||
hasUserElements = await userElement.isVisible({ timeout: 500 })
|
hasUserElements = await userElement.isVisible({ timeout: 500 })
|
||||||
if (hasUserElements) {
|
if (hasUserElements) {
|
||||||
console.log('✅ Found user-specific elements')
|
console.log('SUCCESS: Found user-specific elements')
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Element not found, continue checking
|
// Element not found, continue checking
|
||||||
@@ -1059,7 +1059,7 @@ export class TradingViewAutomation {
|
|||||||
for (const selector of errorSelectors) {
|
for (const selector of errorSelectors) {
|
||||||
if (await this.page.locator(selector).isVisible({ timeout: 500 })) {
|
if (await this.page.locator(selector).isVisible({ timeout: 500 })) {
|
||||||
const errorText = await this.page.locator(selector).textContent()
|
const errorText = await this.page.locator(selector).textContent()
|
||||||
console.log(`❌ Login error detected: ${errorText}`)
|
console.log(`ERROR: Login error detected: ${errorText}`)
|
||||||
throw new Error(`Login failed: ${errorText}`)
|
throw new Error(`Login failed: ${errorText}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1088,7 +1088,7 @@ export class TradingViewAutomation {
|
|||||||
const loginSuccessful = await this.checkLoginStatus()
|
const loginSuccessful = await this.checkLoginStatus()
|
||||||
|
|
||||||
if (loginSuccessful) {
|
if (loginSuccessful) {
|
||||||
console.log('✅ Login verified successful!')
|
console.log('SUCCESS: Login verified successful!')
|
||||||
this.isAuthenticated = true
|
this.isAuthenticated = true
|
||||||
|
|
||||||
// Save session for future use
|
// Save session for future use
|
||||||
@@ -1097,19 +1097,19 @@ export class TradingViewAutomation {
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
console.log('❌ Login verification failed')
|
console.log('ERROR: Login verification failed')
|
||||||
await this.takeDebugScreenshot('login_verification_failed')
|
await this.takeDebugScreenshot('login_verification_failed')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Login completion timeout or error:', error)
|
console.error('ERROR: Login completion timeout or error:', error)
|
||||||
await this.takeDebugScreenshot('login_timeout')
|
await this.takeDebugScreenshot('login_timeout')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Login failed:', error)
|
console.error('ERROR: Login failed:', error)
|
||||||
await this.takeDebugScreenshot('login_error')
|
await this.takeDebugScreenshot('login_error')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -1122,7 +1122,7 @@ export class TradingViewAutomation {
|
|||||||
if (!this.page) throw new Error('Page not initialized')
|
if (!this.page) throw new Error('Page not initialized')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('🔍 Attempting smart login with session persistence...')
|
console.log('CHECKING: Attempting smart login with session persistence...')
|
||||||
|
|
||||||
// First check if already logged in
|
// First check if already logged in
|
||||||
const alreadyLoggedIn = await this.checkLoginStatus()
|
const alreadyLoggedIn = await this.checkLoginStatus()
|
||||||
@@ -1136,7 +1136,7 @@ export class TradingViewAutomation {
|
|||||||
|
|
||||||
// Before attempting login, check if we have any saved session data
|
// Before attempting login, check if we have any saved session data
|
||||||
const sessionInfo = await this.testSessionPersistence()
|
const sessionInfo = await this.testSessionPersistence()
|
||||||
console.log('📊 Session persistence check:', sessionInfo)
|
console.log('DATA: Session persistence check:', sessionInfo)
|
||||||
|
|
||||||
if (sessionInfo.cookiesCount > 0) {
|
if (sessionInfo.cookiesCount > 0) {
|
||||||
console.log('🍪 Found saved session data, attempting to restore...')
|
console.log('🍪 Found saved session data, attempting to restore...')
|
||||||
@@ -1156,15 +1156,15 @@ export class TradingViewAutomation {
|
|||||||
|
|
||||||
const nowLoggedIn = await this.checkLoginStatus()
|
const nowLoggedIn = await this.checkLoginStatus()
|
||||||
if (nowLoggedIn) {
|
if (nowLoggedIn) {
|
||||||
console.log('✅ Session restoration successful! Login confirmed.')
|
console.log('SUCCESS: Session restoration successful! Login confirmed.')
|
||||||
this.isAuthenticated = true
|
this.isAuthenticated = true
|
||||||
await this.saveSession()
|
await this.saveSession()
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
console.log('⚠️ Session restoration failed, saved session may be expired')
|
console.log('WARNING: Session restoration failed, saved session may be expired')
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('❌ Session restoration attempt failed:', e)
|
console.log('ERROR: Session restoration attempt failed:', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1174,7 +1174,7 @@ export class TradingViewAutomation {
|
|||||||
const autoLoginSuccess = await this.login(credentials)
|
const autoLoginSuccess = await this.login(credentials)
|
||||||
|
|
||||||
if (autoLoginSuccess) {
|
if (autoLoginSuccess) {
|
||||||
console.log('✅ Automated login successful! Saving session for future use.')
|
console.log('SUCCESS: Automated login successful! Saving session for future use.')
|
||||||
this.isAuthenticated = true
|
this.isAuthenticated = true
|
||||||
await this.saveSession()
|
await this.saveSession()
|
||||||
return true
|
return true
|
||||||
@@ -1192,14 +1192,14 @@ export class TradingViewAutomation {
|
|||||||
throw loginError
|
throw loginError
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('❌ Automated login failed, this is likely due to captcha protection.')
|
console.log('ERROR: Automated login failed, this is likely due to captcha protection.')
|
||||||
console.log('⚠️ In Docker environment, manual login is not practical.')
|
console.log('WARNING: In Docker environment, manual login is not practical.')
|
||||||
console.log('<27> Checking if we can proceed with session persistence...')
|
console.log('<27> Checking if we can proceed with session persistence...')
|
||||||
|
|
||||||
// Try to check if there are any existing valid session cookies
|
// Try to check if there are any existing valid session cookies
|
||||||
const sessionInfo = await this.testSessionPersistence()
|
const fallbackSessionInfo = await this.testSessionPersistence()
|
||||||
if (sessionInfo.isValid) {
|
if (fallbackSessionInfo.isValid) {
|
||||||
console.log('✅ Found valid session data, attempting to use it...')
|
console.log('SUCCESS: Found valid session data, attempting to use it...')
|
||||||
try {
|
try {
|
||||||
// Navigate to main TradingView page to test session
|
// Navigate to main TradingView page to test session
|
||||||
await this.page.goto('https://www.tradingview.com/', {
|
await this.page.goto('https://www.tradingview.com/', {
|
||||||
@@ -1211,22 +1211,22 @@ export class TradingViewAutomation {
|
|||||||
|
|
||||||
const nowLoggedIn = await this.checkLoginStatus()
|
const nowLoggedIn = await this.checkLoginStatus()
|
||||||
if (nowLoggedIn) {
|
if (nowLoggedIn) {
|
||||||
console.log('✅ Session persistence worked! Login successful.')
|
console.log('SUCCESS: Session persistence worked! Login successful.')
|
||||||
this.isAuthenticated = true
|
this.isAuthenticated = true
|
||||||
await this.saveSession()
|
await this.saveSession()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('❌ Session persistence test failed:', e)
|
console.log('ERROR: Session persistence test failed:', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('❌ All login methods failed. This may require manual intervention.')
|
console.log('ERROR: All login methods failed. This may require manual intervention.')
|
||||||
console.log('💡 To fix: Log in manually in a browser with the same credentials and restart the application.')
|
console.log('💡 To fix: Log in manually in a browser with the same credentials and restart the application.')
|
||||||
return false
|
return false
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Smart login failed:', error)
|
console.error('ERROR: Smart login failed:', error)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1241,7 +1241,7 @@ export class TradingViewAutomation {
|
|||||||
// Validate session integrity before proceeding
|
// Validate session integrity before proceeding
|
||||||
const sessionValid = await this.validateSessionIntegrity()
|
const sessionValid = await this.validateSessionIntegrity()
|
||||||
if (!sessionValid) {
|
if (!sessionValid) {
|
||||||
console.log('⚠️ Session integrity compromised, may require re-authentication')
|
console.log('WARNING: Session integrity compromised, may require re-authentication')
|
||||||
}
|
}
|
||||||
|
|
||||||
const { symbol = 'SOLUSD', timeframe = '5', waitForChart = true } = options
|
const { symbol = 'SOLUSD', timeframe = '5', waitForChart = true } = options
|
||||||
@@ -1458,7 +1458,7 @@ export class TradingViewAutomation {
|
|||||||
await this.takeDebugScreenshot('before_timeframe_change')
|
await this.takeDebugScreenshot('before_timeframe_change')
|
||||||
|
|
||||||
// CRITICAL: Click the interval legend to open timeframe selector
|
// CRITICAL: Click the interval legend to open timeframe selector
|
||||||
console.log('🎯 Looking for interval legend to open timeframe selector...')
|
console.log('TARGET: Looking for interval legend to open timeframe selector...')
|
||||||
const intervalLegendSelectors = [
|
const intervalLegendSelectors = [
|
||||||
'[data-name="legend-source-interval"]',
|
'[data-name="legend-source-interval"]',
|
||||||
'.intervalTitle-l31H9iuA',
|
'.intervalTitle-l31H9iuA',
|
||||||
@@ -1473,7 +1473,7 @@ export class TradingViewAutomation {
|
|||||||
console.log(`Trying interval legend selector: ${selector}`)
|
console.log(`Trying interval legend selector: ${selector}`)
|
||||||
const element = this.page.locator(selector).first()
|
const element = this.page.locator(selector).first()
|
||||||
if (await element.isVisible({ timeout: 3000 })) {
|
if (await element.isVisible({ timeout: 3000 })) {
|
||||||
console.log(`✅ Found interval legend: ${selector}`)
|
console.log(`SUCCESS: Found interval legend: ${selector}`)
|
||||||
await element.click()
|
await element.click()
|
||||||
await this.page.waitForTimeout(2000)
|
await this.page.waitForTimeout(2000)
|
||||||
console.log('🖱️ Clicked interval legend - timeframe selector should be open')
|
console.log('🖱️ Clicked interval legend - timeframe selector should be open')
|
||||||
@@ -1486,13 +1486,13 @@ export class TradingViewAutomation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!intervalLegendClicked) {
|
if (!intervalLegendClicked) {
|
||||||
console.log('❌ Could not find interval legend to click')
|
console.log('ERROR: Could not find interval legend to click')
|
||||||
await this.takeDebugScreenshot('no_interval_legend')
|
await this.takeDebugScreenshot('no_interval_legend')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now look for timeframe options in the opened selector
|
// Now look for timeframe options in the opened selector
|
||||||
console.log('🔍 Looking for timeframe options in selector...')
|
console.log('CHECKING: Looking for timeframe options in selector...')
|
||||||
|
|
||||||
for (const tf of timeframesToTry) {
|
for (const tf of timeframesToTry) {
|
||||||
const timeframeSelectors = [
|
const timeframeSelectors = [
|
||||||
@@ -1531,7 +1531,7 @@ export class TradingViewAutomation {
|
|||||||
// Check if element exists and is visible
|
// Check if element exists and is visible
|
||||||
const isVisible = await element.isVisible({ timeout: 2000 })
|
const isVisible = await element.isVisible({ timeout: 2000 })
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
console.log(`✅ Found timeframe option: ${selector}`)
|
console.log(`SUCCESS: Found timeframe option: ${selector}`)
|
||||||
await element.click()
|
await element.click()
|
||||||
await this.page.waitForTimeout(2000)
|
await this.page.waitForTimeout(2000)
|
||||||
console.log(`🎉 Successfully clicked timeframe option for ${tf}`)
|
console.log(`🎉 Successfully clicked timeframe option for ${tf}`)
|
||||||
@@ -1569,10 +1569,10 @@ export class TradingViewAutomation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (found) {
|
if (found) {
|
||||||
console.log(`✅ Successfully changed timeframe to ${timeframe}`)
|
console.log(`SUCCESS: Successfully changed timeframe to ${timeframe}`)
|
||||||
await this.takeDebugScreenshot('after_timeframe_change')
|
await this.takeDebugScreenshot('after_timeframe_change')
|
||||||
} else {
|
} else {
|
||||||
console.log(`❌ Could not change timeframe to ${timeframe} - timeframe options not found`)
|
console.log(`ERROR: Could not change timeframe to ${timeframe} - timeframe options not found`)
|
||||||
// Take a debug screenshot to see current state
|
// Take a debug screenshot to see current state
|
||||||
await this.takeDebugScreenshot('timeframe_change_failed')
|
await this.takeDebugScreenshot('timeframe_change_failed')
|
||||||
|
|
||||||
@@ -1636,11 +1636,11 @@ export class TradingViewAutomation {
|
|||||||
currentUrl
|
currentUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('📊 Current session info:', result)
|
console.log('DATA: Current session info:', result)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error testing session persistence:', error)
|
console.error('ERROR: Error testing session persistence:', error)
|
||||||
return { isValid: false, cookiesCount: 0, hasStorage: false, currentUrl: 'about:blank' }
|
return { isValid: false, cookiesCount: 0, hasStorage: false, currentUrl: 'about:blank' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1683,7 +1683,7 @@ export class TradingViewAutomation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!chartFound) {
|
if (!chartFound) {
|
||||||
console.log('⚠️ No chart elements found')
|
console.log('WARNING: No chart elements found')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1705,7 +1705,7 @@ export class TradingViewAutomation {
|
|||||||
console.log('Chart data loaded successfully')
|
console.log('Chart data loaded successfully')
|
||||||
return hasData
|
return hasData
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error waiting for chart data:', error)
|
console.error('ERROR: Error waiting for chart data:', error)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1737,7 +1737,7 @@ export class TradingViewAutomation {
|
|||||||
console.log(`Screenshot saved: ${filename}`)
|
console.log(`Screenshot saved: ${filename}`)
|
||||||
return filePath
|
return filePath
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error taking screenshot:', error)
|
console.error('ERROR: Error taking screenshot:', error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1764,7 +1764,7 @@ export class TradingViewAutomation {
|
|||||||
|
|
||||||
console.log(`Screenshot saved: ${filename}`)
|
console.log(`Screenshot saved: ${filename}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('⚠️ Error taking debug screenshot:', error)
|
console.log('WARNING: Error taking debug screenshot:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1793,7 +1793,7 @@ export class TradingViewAutomation {
|
|||||||
try {
|
try {
|
||||||
await this.page.close()
|
await this.page.close()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('⚠️ Error closing page:', e)
|
console.log('WARNING: Error closing page:', e)
|
||||||
}
|
}
|
||||||
this.page = null
|
this.page = null
|
||||||
}
|
}
|
||||||
@@ -1802,7 +1802,7 @@ export class TradingViewAutomation {
|
|||||||
try {
|
try {
|
||||||
await this.context.close()
|
await this.context.close()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('⚠️ Error closing context:', e)
|
console.log('WARNING: Error closing context:', e)
|
||||||
}
|
}
|
||||||
this.context = null
|
this.context = null
|
||||||
}
|
}
|
||||||
@@ -1811,7 +1811,7 @@ export class TradingViewAutomation {
|
|||||||
try {
|
try {
|
||||||
await this.browser.close()
|
await this.browser.close()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('⚠️ Error closing browser:', e)
|
console.log('WARNING: Error closing browser:', e)
|
||||||
}
|
}
|
||||||
this.browser = null
|
this.browser = null
|
||||||
}
|
}
|
||||||
@@ -1822,7 +1822,7 @@ export class TradingViewAutomation {
|
|||||||
this.initPromise = null
|
this.initPromise = null
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error during force cleanup:', error)
|
console.error('ERROR: Error during force cleanup:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1848,13 +1848,13 @@ export class TradingViewAutomation {
|
|||||||
const cookiesData = await fs.readFile(COOKIES_FILE, 'utf8')
|
const cookiesData = await fs.readFile(COOKIES_FILE, 'utf8')
|
||||||
const cookies = JSON.parse(cookiesData)
|
const cookies = JSON.parse(cookiesData)
|
||||||
await this.context!.addCookies(cookies)
|
await this.context!.addCookies(cookies)
|
||||||
console.log(`✅ Loaded ${cookies.length} cookies from saved session`)
|
console.log(`SUCCESS: Loaded ${cookies.length} cookies from saved session`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Session storage will be loaded after page navigation
|
// Note: Session storage will be loaded after page navigation
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('⚠️ Could not load session data (starting fresh):', error)
|
console.log('WARNING: Could not load session data (starting fresh):', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1870,7 +1870,7 @@ export class TradingViewAutomation {
|
|||||||
// Save cookies
|
// Save cookies
|
||||||
const cookies = await this.context.cookies()
|
const cookies = await this.context.cookies()
|
||||||
await fs.writeFile(COOKIES_FILE, JSON.stringify(cookies, null, 2))
|
await fs.writeFile(COOKIES_FILE, JSON.stringify(cookies, null, 2))
|
||||||
console.log(`✅ Saved ${cookies.length} cookies`)
|
console.log(`SUCCESS: Saved ${cookies.length} cookies`)
|
||||||
|
|
||||||
// Save session storage and localStorage
|
// Save session storage and localStorage
|
||||||
const sessionData = await this.page.evaluate(() => {
|
const sessionData = await this.page.evaluate(() => {
|
||||||
@@ -1897,10 +1897,10 @@ export class TradingViewAutomation {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await fs.writeFile(SESSION_STORAGE_FILE, JSON.stringify(sessionData, null, 2))
|
await fs.writeFile(SESSION_STORAGE_FILE, JSON.stringify(sessionData, null, 2))
|
||||||
console.log('✅ Saved session storage and localStorage')
|
console.log('SUCCESS: Saved session storage and localStorage')
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Failed to save session data:', error)
|
console.error('ERROR: Failed to save session data:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1937,10 +1937,10 @@ export class TradingViewAutomation {
|
|||||||
}
|
}
|
||||||
}, sessionData)
|
}, sessionData)
|
||||||
|
|
||||||
console.log('✅ Restored session storage and localStorage')
|
console.log('SUCCESS: Restored session storage and localStorage')
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('⚠️ Could not restore session storage:', error)
|
console.log('WARNING: Could not restore session storage:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1965,17 +1965,17 @@ export class TradingViewAutomation {
|
|||||||
// Verify still logged in
|
// Verify still logged in
|
||||||
const stillLoggedIn = await this.checkLoginStatus()
|
const stillLoggedIn = await this.checkLoginStatus()
|
||||||
if (stillLoggedIn) {
|
if (stillLoggedIn) {
|
||||||
console.log('✅ Session refreshed successfully')
|
console.log('SUCCESS: Session refreshed successfully')
|
||||||
await this.saveSession() // Save refreshed session
|
await this.saveSession() // Save refreshed session
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
console.log('❌ Session expired during refresh')
|
console.log('ERROR: Session expired during refresh')
|
||||||
this.isAuthenticated = false
|
this.isAuthenticated = false
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Failed to refresh session:', error)
|
console.error('ERROR: Failed to refresh session:', error)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1989,25 +1989,25 @@ export class TradingViewAutomation {
|
|||||||
|
|
||||||
if (await this.fileExists(COOKIES_FILE)) {
|
if (await this.fileExists(COOKIES_FILE)) {
|
||||||
await fs.unlink(COOKIES_FILE)
|
await fs.unlink(COOKIES_FILE)
|
||||||
console.log('✅ Cleared cookies file')
|
console.log('SUCCESS: Cleared cookies file')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await this.fileExists(SESSION_STORAGE_FILE)) {
|
if (await this.fileExists(SESSION_STORAGE_FILE)) {
|
||||||
await fs.unlink(SESSION_STORAGE_FILE)
|
await fs.unlink(SESSION_STORAGE_FILE)
|
||||||
console.log('✅ Cleared session storage file')
|
console.log('SUCCESS: Cleared session storage file')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear browser context storage if available
|
// Clear browser context storage if available
|
||||||
if (this.context) {
|
if (this.context) {
|
||||||
await this.context.clearCookies()
|
await this.context.clearCookies()
|
||||||
console.log('✅ Cleared browser context cookies')
|
console.log('SUCCESS: Cleared browser context cookies')
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isAuthenticated = false
|
this.isAuthenticated = false
|
||||||
console.log('✅ Session data cleared successfully')
|
console.log('SUCCESS: Session data cleared successfully')
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Failed to clear session data:', error)
|
console.error('ERROR: Failed to clear session data:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2094,7 +2094,7 @@ export class TradingViewAutomation {
|
|||||||
await new Promise(resolve => setTimeout(resolve, Math.random() * 300 + 100))
|
await new Promise(resolve => setTimeout(resolve, Math.random() * 300 + 100))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('⚠️ Error simulating mouse movement:', error)
|
console.log('WARNING: Error simulating mouse movement:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2115,7 +2115,7 @@ export class TradingViewAutomation {
|
|||||||
await new Promise(resolve => setTimeout(resolve, Math.random() * 800 + 300))
|
await new Promise(resolve => setTimeout(resolve, Math.random() * 800 + 300))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('⚠️ Error simulating scrolling:', error)
|
console.log('WARNING: Error simulating scrolling:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2179,7 +2179,7 @@ export class TradingViewAutomation {
|
|||||||
this.sessionFingerprint = fingerprint
|
this.sessionFingerprint = fingerprint
|
||||||
return fingerprint
|
return fingerprint
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error generating session fingerprint:', error)
|
console.error('ERROR: Error generating session fingerprint:', error)
|
||||||
return `fallback-${Date.now()}`
|
return `fallback-${Date.now()}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2206,7 +2206,7 @@ export class TradingViewAutomation {
|
|||||||
for (const indicator of invalidationIndicators) {
|
for (const indicator of invalidationIndicators) {
|
||||||
try {
|
try {
|
||||||
if (await this.page.locator(indicator).isVisible({ timeout: 1000 })) {
|
if (await this.page.locator(indicator).isVisible({ timeout: 1000 })) {
|
||||||
console.log(`⚠️ Session invalidation detected: ${indicator}`)
|
console.log(`WARNING: Session invalidation detected: ${indicator}`)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -2224,14 +2224,14 @@ export class TradingViewAutomation {
|
|||||||
if (stored.userAgent !== current.userAgent ||
|
if (stored.userAgent !== current.userAgent ||
|
||||||
stored.platform !== current.platform ||
|
stored.platform !== current.platform ||
|
||||||
stored.language !== current.language) {
|
stored.language !== current.language) {
|
||||||
console.log('⚠️ Session fingerprint mismatch detected')
|
console.log('WARNING: Session fingerprint mismatch detected')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error validating session integrity:', error)
|
console.error('ERROR: Error validating session integrity:', error)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2262,7 +2262,7 @@ export class TradingViewAutomation {
|
|||||||
// Wait a bit longer to let the page settle
|
// Wait a bit longer to let the page settle
|
||||||
await this.humanDelay(2000, 4000)
|
await this.humanDelay(2000, 4000)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('⚠️ Error performing human-like interactions:', error)
|
console.log('WARNING: Error performing human-like interactions:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2288,9 +2288,9 @@ export class TradingViewAutomation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await fs.writeFile(captchaMarkerFile, JSON.stringify(markerData, null, 2))
|
await fs.writeFile(captchaMarkerFile, JSON.stringify(markerData, null, 2))
|
||||||
console.log(`📝 Marked captcha detection #${markerData.count} at ${markerData.timestamp}`)
|
console.log(`INFO: Marked captcha detection #${markerData.count} at ${markerData.timestamp}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('⚠️ Error marking captcha detection:', error)
|
console.log('WARNING: Error marking captcha detection:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user