diff --git a/debug_layout_menu_Diy_module.png b/debug_layout_menu_Diy_module.png new file mode 100644 index 0000000..5283f26 Binary files /dev/null and b/debug_layout_menu_Diy_module.png differ diff --git a/debug_layout_menu_ai.png b/debug_layout_menu_ai.png new file mode 100644 index 0000000..1a0f3c3 Binary files /dev/null and b/debug_layout_menu_ai.png differ diff --git a/lib/tradingview.ts b/lib/tradingview.ts index a680b62..e3efbf8 100644 --- a/lib/tradingview.ts +++ b/lib/tradingview.ts @@ -8,6 +8,13 @@ const TRADINGVIEW_PASSWORD = process.env.TRADINGVIEW_PASSWORD const TRADINGVIEW_LAYOUTS = (process.env.TRADINGVIEW_LAYOUTS || '').split(',').map(l => l.trim()) const PUPPETEER_EXECUTABLE_PATH = process.env.PUPPETEER_EXECUTABLE_PATH || '/usr/bin/chromium' +// Layout name to URL mapping +const LAYOUT_URLS: { [key: string]: string } = { + 'ai': 'Z1TzpUrf', + 'Diy module': 'vWVvjLhP', + // Add more layout mappings as needed +} + export class TradingViewCapture { private browser: Browser | null = null private page: Page | null = null @@ -211,19 +218,6 @@ export class TradingViewCapture { console.log('Using settings:', { symbol: finalSymbol, timeframe: finalTimeframe, layouts: finalLayouts }) const page = await this.init() - // Add timeframe to TradingView URL if provided - let url = `https://www.tradingview.com/chart/?symbol=${finalSymbol}` - if (finalTimeframe) { - url += `&interval=${encodeURIComponent(finalTimeframe)}` - } - try { - console.log('Navigating to TradingView chart:', url) - await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 }) - console.log('Successfully navigated to chart') - } catch (e: any) { - console.error('Failed to load TradingView chart page:', e) - throw new Error('Failed to load TradingView chart page: ' + (e.message || e)) - } // Capture screenshots for each layout const screenshots: string[] = [] @@ -232,8 +226,45 @@ export class TradingViewCapture { const layout = finalLayouts[i] console.log(`Processing layout ${i + 1}/${finalLayouts.length}: ${layout}`) - // Load the layout - await this.loadLayout(page, layout) + // Check if we have a direct URL for this layout + const layoutUrlPath = LAYOUT_URLS[layout] + if (layoutUrlPath) { + // Use direct layout URL + let url = `https://www.tradingview.com/chart/${layoutUrlPath}/?symbol=${finalSymbol}` + if (finalTimeframe) { + url += `&interval=${encodeURIComponent(finalTimeframe)}` + } + + try { + console.log('Navigating to layout URL:', url) + await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 }) + console.log('Successfully navigated to layout:', layout) + } catch (e: any) { + console.error(`Failed to load layout "${layout}":`, e) + throw new Error(`Failed to load layout "${layout}": ` + (e.message || e)) + } + } else { + // Fallback to loading layout via menu (for layouts without direct URLs) + console.log(`No direct URL found for layout "${layout}", trying menu navigation...`) + + // Navigate to base chart URL first + let url = `https://www.tradingview.com/chart/?symbol=${finalSymbol}` + if (finalTimeframe) { + url += `&interval=${encodeURIComponent(finalTimeframe)}` + } + + try { + console.log('Navigating to base chart URL:', url) + await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 }) + console.log('Successfully navigated to base chart') + } catch (e: any) { + console.error('Failed to load TradingView chart page:', e) + throw new Error('Failed to load TradingView chart page: ' + (e.message || e)) + } + + // Try to load the layout via menu + await this.loadLayout(page, layout) + } // Wait for layout to load await new Promise(res => setTimeout(res, 3000)) @@ -261,229 +292,45 @@ export class TradingViewCapture { } private async loadLayout(page: Page, layout: string): Promise { - try { - console.log('Trying to load layout:', layout) - - // Try multiple selectors for the layout button - const layoutSelectors = [ - '[data-name="load-chart-layout-dialog"]', - '[data-name="layouts-menu"]', - '[data-name="chart-layout-button"]', - 'button[title*="Layout" i]', - 'button[aria-label*="Layout" i]', - '[data-testid*="layout"]' - ] - - let layoutButton = null - for (const selector of layoutSelectors) { - try { - layoutButton = await page.waitForSelector(selector, { timeout: 3000 }) - if (layoutButton) { - console.log('Found layout button with selector:', selector) - break - } - } catch (e) { - // Continue to next selector - } - } - - if (!layoutButton) { - // Try to find layout button by text content - const buttons = await page.$$('button, [role="button"]') - for (const btn of buttons) { - const text = await page.evaluate(el => { - const element = el as HTMLElement - return element.innerText || element.textContent || element.title || element.getAttribute('aria-label') - }, btn) - if (text && text.toLowerCase().includes('layout')) { - layoutButton = btn - console.log('Found layout button by text:', text) - break - } - } - } - - if (layoutButton) { - await layoutButton.click() - console.log('Clicked layout button') - - // Wait longer for the layout menu to appear - await new Promise(res => setTimeout(res, 2000)) - - // Take a debug screenshot of the layout menu - const debugMenuPath = path.resolve(`debug_layout_menu_${layout.replace(/\s+/g, '_')}.png`) as `${string}.png` - await page.screenshot({ path: debugMenuPath }) - console.log('Layout menu screenshot saved:', debugMenuPath) - - // Look for layout menu items with more specific selectors - const layoutItemSelectors = [ - `[data-name="chart-layout-list-item"]`, - `[data-testid*="layout"]`, - `.layout-item`, - `[role="option"]`, - `[role="menuitem"]`, - `.tv-dropdown-behavior__item`, - `.tv-menu__item`, - `li[data-value*="${layout}"]`, - `div[data-layout-name="${layout}"]` - ] - - let layoutItem = null - let foundMethod = '' - let foundElement = false - - // Try to find layout item by exact text match first - for (const selector of layoutItemSelectors) { - try { - console.log(`Trying selector: ${selector}`) - const items = await page.$$(selector) - console.log(`Found ${items.length} items with selector: ${selector}`) - - for (const item of items) { - const text = await page.evaluate(el => { - const element = el as HTMLElement - return (element.innerText || element.textContent || '').trim() - }, item) - console.log(`Item text: "${text}"`) - - if (text && text.toLowerCase() === layout.toLowerCase()) { - layoutItem = item - foundMethod = `exact match with selector: ${selector}` - break - } - } - if (layoutItem) break - } catch (e) { - console.log(`Error with selector ${selector}:`, e) - } - } - - // If no exact match, try partial match - if (!layoutItem) { - for (const selector of layoutItemSelectors) { - try { - const items = await page.$$(selector) - for (const item of items) { - const text = await page.evaluate(el => { - const element = el as HTMLElement - return (element.innerText || element.textContent || '').trim() - }, item) - - if (text && text.toLowerCase().includes(layout.toLowerCase())) { - layoutItem = item - foundMethod = `partial match with selector: ${selector}` - break - } - } - if (layoutItem) break - } catch (e) { - // Continue to next selector - } - } - } - - // If still no match, try a more comprehensive search - if (!layoutItem) { - console.log('No layout item found with standard selectors, trying comprehensive search...') - const foundElement = await page.evaluate((layout) => { - const allElements = Array.from(document.querySelectorAll('*')) - - // Look for elements that contain the layout name - const candidates = allElements.filter(el => { - const text = (el.textContent || '').trim() - return text && text.toLowerCase().includes(layout.toLowerCase()) - }) - - console.log('Found candidates:', candidates.map(el => ({ - tag: el.tagName, - text: el.textContent?.trim(), - classes: el.className - }))) - - // Prioritize clickable elements - const clickable = candidates.find(el => - el.tagName === 'BUTTON' || - el.tagName === 'A' || - el.hasAttribute('role') || - el.classList.contains('item') || - el.classList.contains('option') || - el.classList.contains('menu') - ) - - if (clickable) { - (clickable as HTMLElement).click() - return true - } - - // Fall back to exact text match - const exactMatch = candidates.find(el => - (el.textContent || '').trim().toLowerCase() === layout.toLowerCase() - ) - - if (exactMatch) { - (exactMatch as HTMLElement).click() - return true - } - - return false - }, layout) - - if (foundElement) { - foundMethod = 'comprehensive search with click' - console.log(`Found and clicked layout item "${layout}" using: ${foundMethod}`) - } - } - - if (layoutItem) { - console.log(`Found layout item "${layout}" using: ${foundMethod}`) - await layoutItem.click() - console.log('Clicked layout item:', layout) - - // Wait for layout to actually load - await new Promise(res => setTimeout(res, 5000)) - - // Take a screenshot after layout change - const debugAfterPath = path.resolve(`debug_after_layout_${layout.replace(/\s+/g, '_')}.png`) as `${string}.png` - await page.screenshot({ path: debugAfterPath }) - console.log('After layout change screenshot saved:', debugAfterPath) - - } else if (foundElement && foundMethod === 'comprehensive search with click') { - console.log('Layout item was clicked via comprehensive search') - - // Wait for layout to actually load - await new Promise(res => setTimeout(res, 5000)) - - // Take a screenshot after layout change - const debugAfterPath = path.resolve(`debug_after_layout_${layout.replace(/\s+/g, '_')}.png`) as `${string}.png` - await page.screenshot({ path: debugAfterPath }) - console.log('After layout change screenshot saved:', debugAfterPath) - - } else { - console.log('Layout item not found with any method') - - // List all text content on the page for debugging - const allTexts = await page.evaluate(() => { - const elements = Array.from(document.querySelectorAll('*')) - return elements - .map(el => (el.textContent || '').trim()) - .filter(text => text && text.length > 0 && text.length < 100) - .slice(0, 50) // Limit to first 50 for debugging - }) - console.log('Available texts on page:', allTexts) - } - } else { - console.log('Layout button not found, skipping layout loading') - } - - console.log('Layout loading completed for:', layout) - } catch (e: any) { - const debugLayoutErrorPath = path.resolve(`debug_layout_error_${layout.replace(/\s+/g, '_')}.png`) as `${string}.png` - await page.screenshot({ path: debugLayoutErrorPath }) - console.error('TradingView layout not found or could not be loaded:', e) - console.log('Continuing without layout...') - // Don't throw error, just continue without layout + try { + console.log('Loading layout using direct URL:', layout) + + // Check if we have a direct URL for this layout + const layoutUrlPath = LAYOUT_URLS[layout] + if (!layoutUrlPath) { + console.log(`No direct URL found for layout "${layout}". Available layouts:`, Object.keys(LAYOUT_URLS)) + console.log('Skipping layout loading and continuing with default chart') + return } + + // Construct the full URL for the layout + const layoutUrl = `https://www.tradingview.com/chart/${layoutUrlPath}/` + console.log('Navigating to layout URL:', layoutUrl) + + // Navigate directly to the layout URL + await page.goto(layoutUrl, { waitUntil: 'networkidle2', timeout: 60000 }) + console.log('Successfully navigated to layout:', layout) + + // Wait for the layout to fully load + await new Promise(res => setTimeout(res, 3000)) + + // Take a screenshot after layout loads for debugging + const debugAfterPath = path.resolve(`debug_after_layout_${layout.replace(/\s+/g, '_')}.png`) as `${string}.png` + await page.screenshot({ path: debugAfterPath }) + console.log('After layout load screenshot saved:', debugAfterPath) + + } catch (e: any) { + console.error(`Failed to load layout "${layout}":`, e) + + // Take debug screenshot on error + const debugErrorPath = path.resolve(`debug_layout_error_${layout.replace(/\s+/g, '_')}.png`) as `${string}.png` + await page.screenshot({ path: debugErrorPath }) + console.log('Layout error screenshot saved:', debugErrorPath) + + // Don't throw error, just continue with default chart + console.log('Continuing with default chart layout...') } + } } export const tradingViewCapture = new TradingViewCapture() diff --git a/screenshots/SOLUSD_5_1752065182442_Diy module.png b/screenshots/SOLUSD_5_1752065182442_Diy module.png new file mode 100644 index 0000000..e05e6cc Binary files /dev/null and b/screenshots/SOLUSD_5_1752065182442_Diy module.png differ diff --git a/screenshots/SOLUSD_5_1752065182442_ai.png b/screenshots/SOLUSD_5_1752065182442_ai.png new file mode 100644 index 0000000..b45c075 Binary files /dev/null and b/screenshots/SOLUSD_5_1752065182442_ai.png differ