quote.js
1 /** 2 * Barchart stock quote — price, volume, market cap, P/E, EPS, and key metrics. 3 * Auth: CSRF token from <meta name="csrf-token"> + session cookies. 4 */ 5 import { cli, Strategy } from '@jackwener/opencli/registry'; 6 import { CommandExecutionError } from '@jackwener/opencli/errors'; 7 cli({ 8 site: 'barchart', 9 name: 'quote', 10 description: 'Barchart stock quote with price, volume, and key metrics', 11 domain: 'www.barchart.com', 12 strategy: Strategy.COOKIE, 13 args: [ 14 { name: 'symbol', required: true, positional: true, help: 'Stock ticker (e.g. AAPL, MSFT, TSLA)' }, 15 ], 16 columns: [ 17 'symbol', 'name', 'price', 'change', 'changePct', 18 'open', 'high', 'low', 'prevClose', 'volume', 19 'avgVolume', 'marketCap', 'peRatio', 'eps', 20 ], 21 func: async (page, kwargs) => { 22 const symbol = kwargs.symbol.toUpperCase().trim(); 23 await page.goto(`https://www.barchart.com/stocks/quotes/${encodeURIComponent(symbol)}/overview`); 24 await page.wait(4); 25 const data = await page.evaluate(` 26 (async () => { 27 const sym = ${JSON.stringify(symbol)}; 28 const csrf = document.querySelector('meta[name="csrf-token"]')?.content || ''; 29 30 // Strategy 1: internal proxy API with CSRF token 31 try { 32 const fields = [ 33 'symbol','symbolName','lastPrice','priceChange','percentChange', 34 'highPrice','lowPrice','openPrice','previousPrice','volume','averageVolume', 35 'marketCap','peRatio','earningsPerShare','tradeTime', 36 ].join(','); 37 const url = '/proxies/core-api/v1/quotes/get?symbol=' + encodeURIComponent(sym) + '&fields=' + fields; 38 const resp = await fetch(url, { 39 credentials: 'include', 40 headers: { 'X-CSRF-TOKEN': csrf }, 41 }); 42 if (resp.ok) { 43 const d = await resp.json(); 44 const row = d?.data?.[0] || null; 45 if (row) { 46 return { source: 'api', row }; 47 } 48 } 49 } catch(e) {} 50 51 // Strategy 2: parse from DOM 52 try { 53 const priceEl = document.querySelector('span.last-change'); 54 const price = priceEl ? priceEl.textContent.trim() : null; 55 56 // Change values are sibling spans inside .pricechangerow > .last-change 57 const changeParent = priceEl?.parentElement; 58 const changeSpans = changeParent ? changeParent.querySelectorAll('span') : []; 59 let change = null; 60 let changePct = null; 61 for (const s of changeSpans) { 62 const t = s.textContent.trim(); 63 if (s === priceEl) continue; 64 if (t.includes('%')) changePct = t.replace(/[()]/g, ''); 65 else if (t.match(/^[+-]?[\\d.]+$/)) change = t; 66 } 67 68 // Financial data rows 69 const rows = document.querySelectorAll('.financial-data-row'); 70 const fdata = {}; 71 for (const row of rows) { 72 const spans = row.querySelectorAll('span'); 73 if (spans.length >= 2) { 74 const label = spans[0].textContent.trim(); 75 const valSpan = row.querySelector('span.right span:not(.ng-hide)'); 76 fdata[label] = valSpan ? valSpan.textContent.trim() : ''; 77 } 78 } 79 80 // Day high/low from row chart 81 const dayLow = document.querySelector('.bc-quote-row-chart .small-6:first-child .inline:not(.ng-hide)'); 82 const dayHigh = document.querySelector('.bc-quote-row-chart .text-right .inline:not(.ng-hide)'); 83 const openEl = document.querySelector('.mark span'); 84 const openText = openEl ? openEl.textContent.trim().replace('Open ', '') : null; 85 86 const name = document.querySelector('h1 span.symbol'); 87 88 return { 89 source: 'dom', 90 row: { 91 symbol: sym, 92 symbolName: name ? name.textContent.trim() : sym, 93 lastPrice: price, 94 priceChange: change, 95 percentChange: changePct, 96 open: openText, 97 highPrice: dayHigh ? dayHigh.textContent.trim() : null, 98 lowPrice: dayLow ? dayLow.textContent.trim() : null, 99 previousClose: fdata['Previous Close'] || null, 100 volume: fdata['Volume'] || null, 101 averageVolume: fdata['Average Volume'] || null, 102 marketCap: null, 103 peRatio: null, 104 earningsPerShare: null, 105 } 106 }; 107 } catch(e) { 108 return { error: 'Could not fetch quote for ' + sym + ': ' + e.message }; 109 } 110 })() 111 `); 112 if (!data || data.error) 113 throw new CommandExecutionError(data?.error || `Failed to fetch quote for ${symbol}`); 114 const r = data.row || {}; 115 // API returns formatted strings like "+1.41" and "+0.56%"; use raw if available 116 const raw = r.raw || {}; 117 return [{ 118 symbol: r.symbol || symbol, 119 name: r.symbolName || r.name || symbol, 120 price: r.lastPrice ?? null, 121 change: r.priceChange ?? null, 122 changePct: r.percentChange ?? null, 123 open: r.openPrice ?? r.open ?? null, 124 high: r.highPrice ?? null, 125 low: r.lowPrice ?? null, 126 prevClose: r.previousPrice ?? r.previousClose ?? null, 127 volume: r.volume ?? null, 128 avgVolume: r.averageVolume ?? null, 129 marketCap: r.marketCap ?? null, 130 peRatio: r.peRatio ?? null, 131 eps: r.earningsPerShare ?? null, 132 }]; 133 }, 134 });