Spaces:
Running
Running
| const express = require('express'); | |
| const axios = require('axios'); | |
| const Parser = require('rss-parser'); | |
| const Sentiment = require('sentiment'); | |
| const cron = require('node-cron'); | |
| const cors = require('cors'); | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const compression = require('compression'); | |
| const app = express(); | |
| const parser = new Parser(); | |
| const sentiment = new Sentiment(); | |
| const PORT = 3001; | |
| app.use(compression()); | |
| app.use(cors()); | |
| app.use(express.static('public', { maxAge: '1d' })); | |
| const DATA_FILE = path.join(__dirname, 'data', 'news_sentiment.json'); | |
| const TREND_FILE = path.join(__dirname, 'data', 'sentiment_trends.json'); | |
| const RSS_FEEDS = [ | |
| 'https://news.google.com/rss/search?q=when:48h+stock+market+india+NSE+BSE+finance&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/headlines/section/topic/BUSINESS?hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+stock+market+india+business&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:moneycontrol.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:financialexpress.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:business-standard.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://economictimes.indiatimes.com/markets/stocks/rssfeeds/2146842.cms', | |
| 'https://www.livemint.com/rss/markets', | |
| 'https://news.google.com/rss/search?q=when:48h+site:thehindubusinessline.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:pulse.zerodha.com&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:in.investing.com+intitle:(stocks+OR+markets+OR+india)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:web.stockedge.com&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:groww.in&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:ndtvprofit.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:cnbctv18.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:zeebiz.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:goodreturns.in+intitle:(stocks+OR+markets+OR+shares)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:ticker.finology.in&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:moneyworks4me.com&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:trendlyne.com&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:tickertape.in&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:equitymaster.com+intitle:(stocks+OR+markets+OR+shares)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:marketsmojo.com&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:fortuneindia.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:businessworld.in+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:outlookbusiness.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:money.rediff.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:informistmedia.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:reuters.com+intitle:(india+AND+(stocks+OR+markets+OR+nse+OR+bse))&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:thehindu.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:timesofindia.indiatimes.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:deccanherald.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:bqprime.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:forbesindia.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:bloomberg.com+intitle:(india+AND+(stocks+OR+markets+OR+nse+OR+bse))&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:dsij.in+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:economictimes.indiatimes.com/et-now+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:firstpost.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:capitalmarket.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:news18.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:news.abplive.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:mydigitalfc.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:asia.nikkei.com+intitle:(india+AND+(stocks+OR+markets+OR+nse+OR+bse))&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:icicidirect.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:hdfcsec.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:smallcase.com+intitle:(stocks+OR+markets+OR+investing)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:upstox.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:angelone.in+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:motilaloswal.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:finshots.in+intitle:(stocks+OR+markets+OR+shares)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:tradebrains.in+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:valueresearchonline.com+intitle:(stocks+OR+markets+OR+shares)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:entrackr.com+intitle:(stocks+OR+markets+OR+ipo+OR+listed)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:inc42.com+intitle:(stocks+OR+markets+OR+ipo+OR+listed)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:ft.com+intitle:(india+AND+(stocks+OR+markets+OR+nse+OR+bse))&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:economist.com+intitle:(india+AND+(business+OR+markets+OR+economy))&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:wsj.com+intitle:(india+AND+(stocks+OR+markets+OR+corporate))&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:thestatesman.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:dailypioneer.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:freepressjournal.in+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://www.businesstoday.in/rss/markets', | |
| 'https://news.google.com/rss/search?q=when:48h+site:screener.in&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:capitalmind.in&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:thecore.in&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:bseindia.com+corporate+announcements&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:nseindia.com+corporate+announcements&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+site:alphastreet.com+india&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+intitle:(dividend+OR+buyback+OR+"bonus+issue"+OR+"stock+split")+NSE+BSE&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+intitle:("quarterly+results"+OR+"earnings"+OR+"PAT+growth")+NSE+BSE&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+intitle:("FII+buying"+OR+"DII+selling"+OR+"bulk+deal"+OR+"block+deal")+NSE+BSE&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+intitle:("SEBI"+OR+"RBI+monetary+policy"+OR+"repo+rate")+India&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+intitle:("52-week+high"+OR+"breakout"+OR+"upper+circuit")+NSE+BSE&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+intitle:("IPO+allotment"+OR+"IPO+listing"+OR+"Grey+Market+Premium")+India&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+intitle:("Auto+sales"+OR+"USFDA")+India&hl=en-IN&gl=IN&ceid=IN:en', | |
| 'https://news.google.com/rss/search?q=when:48h+intitle:("Management+change"+OR+"CEO+resignation"+OR+"CFO+appointment")+India&hl=en-IN&gl=IN&ceid=IN:en' | |
| ]; | |
| const BROWSER_HEADERS = { | |
| 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36', | |
| 'Accept': 'application/rss+xml,application/xml;q=0.9,*/*;q=0.8', | |
| 'Accept-Language': 'en-US,en;q=0.9' | |
| }; | |
| let cachedNews = []; | |
| let feedStatus = {}; | |
| let sentimentTrend = []; | |
| let cachedIndices = {}; | |
| let isFetching = false; | |
| function getSourceName(url, feedTitle) { | |
| if (url.includes('NSE+BSE+finance')) return 'Google Finance (Markets)'; | |
| if (url.includes('site:moneycontrol.com')) return 'Moneycontrol'; | |
| if (url.includes('site:financialexpress.com')) return 'Financial Express'; | |
| if (url.includes('site:business-standard.com')) return 'Business Standard'; | |
| if (url.includes('economictimes')) return 'Economic Times'; | |
| if (url.includes('livemint')) return 'LiveMint'; | |
| if (url.includes('headlines/section/topic/BUSINESS')) return 'Business News'; | |
| if (url.includes('site:thehindubusinessline.com')) return 'The Hindu Business Line'; | |
| if (url.includes('site:pulse.zerodha.com')) return 'Zerodha Pulse'; | |
| if (url.includes('site:in.investing.com')) return 'Investing.com'; | |
| if (url.includes('site:web.stockedge.com')) return 'StockEdge'; | |
| if (url.includes('site:groww.in')) return 'Groww'; | |
| if (url.includes('site:ndtvprofit.com')) return 'NDTV Profit'; | |
| if (url.includes('site:cnbctv18.com')) return 'CNBC TV18'; | |
| if (url.includes('site:zeebiz.com')) return 'Zee Business'; | |
| if (url.includes('site:goodreturns.in')) return 'GoodReturns'; | |
| if (url.includes('site:ticker.finology.in')) return 'Finology Ticker'; | |
| if (url.includes('site:moneyworks4me.com')) return 'MoneyWorks4Me'; | |
| if (url.includes('site:trendlyne.com')) return 'Trendlyne'; | |
| if (url.includes('site:tickertape.in')) return 'Tickertape'; | |
| if (url.includes('site:equitymaster.com')) return 'Equitymaster'; | |
| if (url.includes('site:marketsmojo.com')) return 'MarketsMojo'; | |
| if (url.includes('site:fortuneindia.com')) return 'Fortune India'; | |
| if (url.includes('site:businessworld.in')) return 'BusinessWorld'; | |
| if (url.includes('site:outlookbusiness.com')) return 'Outlook Business'; | |
| if (url.includes('site:money.rediff.com')) return 'Rediff Money'; | |
| if (url.includes('site:informistmedia.com')) return 'Informist Media'; | |
| if (url.includes('site:reuters.com')) return 'Reuters India'; | |
| if (url.includes('site:thehindu.com')) return 'The Hindu'; | |
| if (url.includes('site:timesofindia.indiatimes.com')) return 'Times of India'; | |
| if (url.includes('site:deccanherald.com')) return 'Deccan Herald'; | |
| if (url.includes('site:bqprime.com')) return 'BQ Prime'; | |
| if (url.includes('site:forbesindia.com')) return 'Forbes India'; | |
| if (url.includes('site:bloomberg.com')) return 'Bloomberg India'; | |
| if (url.includes('site:dsij.in')) return 'DSIJ'; | |
| if (url.includes('site:economictimes.indiatimes.com/et-now')) return 'ET Now'; | |
| if (url.includes('site:firstpost.com')) return 'Firstpost Business'; | |
| if (url.includes('site:capitalmarket.com')) return 'Capital Market'; | |
| if (url.includes('site:news18.com')) return 'News18 Business'; | |
| if (url.includes('site:news.abplive.com')) return 'ABP Business'; | |
| if (url.includes('site:mydigitalfc.com')) return 'Financial Chronicle'; | |
| if (url.includes('site:asia.nikkei.com')) return 'Nikkei Asia (India)'; | |
| if (url.includes('site:icicidirect.com')) return 'ICICIDirect Research'; | |
| if (url.includes('site:hdfcsec.com')) return 'HDFC Securities'; | |
| if (url.includes('site:smallcase.com')) return 'Smallcase Insights'; | |
| if (url.includes('site:upstox.com')) return 'Upstox Updates'; | |
| if (url.includes('site:angelone.in')) return 'Angel One'; | |
| if (url.includes('businesstoday')) return 'Business Today'; | |
| if (url.includes('site:screener.in')) return 'Screener.in'; | |
| if (url.includes('site:capitalmind.in')) return 'CapitalMind'; | |
| if (url.includes('site:thecore.in')) return 'The Core'; | |
| if (url.includes('site:bseindia.com')) return 'BSE Announcements'; | |
| if (url.includes('site:nseindia.com')) return 'NSE Announcements'; | |
| if (url.includes('site:alphastreet.com')) return 'AlphaStreet India'; | |
| if (url.includes('intitle:(dividend')) return 'Corporate Action (Dividend/Buyback)'; | |
| if (url.includes('intitle:("quarterly+results"')) return 'Earnings Alert'; | |
| if (url.includes('intitle:("FII+buying"')) return 'Institutional Flow Alert'; | |
| if (url.includes('intitle:("SEBI"')) return 'Regulatory/Policy Alert'; | |
| if (url.includes('intitle:("52-week+high"')) return 'Momentum/Breakout Alert'; | |
| if (url.includes('intitle:("IPO+allotment"')) return 'IPO/Listing Alert'; | |
| if (url.includes('intitle:("Auto+sales"')) return 'Sector Update'; | |
| if (url.includes('intitle:("Management+change"')) return 'Management Update'; | |
| if (url.includes('site:motilaloswal.com')) return 'Motilal Oswal'; | |
| if (url.includes('site:finshots.in')) return 'Finshots'; | |
| if (url.includes('site:tradebrains.in')) return 'Trade Brains'; | |
| if (url.includes('site:valueresearchonline.com')) return 'Value Research'; | |
| if (url.includes('site:entrackr.com')) return 'Entrackr (Tech Stocks)'; | |
| if (url.includes('site:inc42.com')) return 'Inc42 (Tech Stocks)'; | |
| if (url.includes('site:ft.com')) return 'Financial Times (India)'; | |
| if (url.includes('site:economist.com')) return 'The Economist (India)'; | |
| if (url.includes('site:wsj.com')) return 'WSJ (India)'; | |
| if (url.includes('site:thestatesman.com')) return 'The Statesman'; | |
| if (url.includes('site:dailypioneer.com')) return 'The Pioneer'; | |
| if (url.includes('site:freepressjournal.in')) return 'Free Press Journal'; | |
| return feedTitle?.replace('Google News - ', '') || 'Market News'; | |
| } | |
| async function fetchIndices() { | |
| const tickers = { | |
| 'NIFTY 50': '^NSEI', | |
| 'BANK NIFTY': '^NSEBANK', | |
| 'SENSEX': '^BSESN', | |
| 'FINNIFTY': 'NIFTY_FIN_SERVICE.NS', | |
| 'INDIA VIX': '^INDIAVIX', | |
| 'USD/INR': 'INR=X' | |
| }; | |
| const tickerEntries = Object.entries(tickers); | |
| // Fetch in parallel but keep the order | |
| const responses = await Promise.allSettled(tickerEntries.map(async ([name, ticker]) => { | |
| const response = await axios.get(`https://query1.finance.yahoo.com/v8/finance/chart/${ticker}?interval=1m&range=1d`, { | |
| headers: { 'User-Agent': BROWSER_HEADERS['User-Agent'] }, | |
| timeout: 5000 | |
| }); | |
| const meta = response.data.chart.result[0].meta; | |
| return { | |
| price: meta.regularMarketPrice.toFixed(2), | |
| change: (meta.regularMarketPrice - meta.previousClose).toFixed(2), | |
| changePercent: (((meta.regularMarketPrice - meta.previousClose) / meta.previousClose) * 100).toFixed(2) | |
| }; | |
| })); | |
| const results = {}; | |
| tickerEntries.forEach(([name], index) => { | |
| const res = responses[index]; | |
| if (res.status === 'fulfilled') { | |
| results[name] = res.value; | |
| } else { | |
| results[name] = cachedIndices[name] || { price: '---', change: '0', changePercent: '0' }; | |
| } | |
| }); | |
| cachedIndices = results; | |
| } | |
| async function fetchAndAnalyzeNews() { | |
| if (isFetching) return; | |
| isFetching = true; | |
| const allEntries = []; | |
| const now = new Date(); | |
| const fortyEightHoursAgo = new Date(now.getTime() - 48 * 60 * 60 * 1000); | |
| const fetchPromises = RSS_FEEDS.map(async (url) => { | |
| try { | |
| const response = await axios.get(url, { headers: BROWSER_HEADERS, timeout: 10000 }); | |
| const feed = await parser.parseString(response.data); | |
| const sourceName = getSourceName(url, feed.title); | |
| feedStatus[url] = { status: 'Success', displayName: sourceName, lastUpdate: new Date().toISOString() }; | |
| const items = []; | |
| feed.items.forEach(item => { | |
| const pubDate = new Date(item.pubDate); | |
| if (pubDate >= fortyEightHoursAgo) { | |
| const fullText = `${item.title} ${item.contentSnippet || ''}`; | |
| const analysis = sentiment.analyze(fullText); | |
| items.push({ | |
| id: item.guid || item.link, | |
| title: item.title, | |
| link: item.link, | |
| pubDate: item.pubDate, | |
| source: sourceName, | |
| sentiment: { | |
| score: analysis.score, | |
| label: analysis.score > 0 ? 'Positive' : (analysis.score < 0 ? 'Negative' : 'Neutral') | |
| } | |
| }); | |
| } | |
| }); | |
| return items; | |
| } catch (e) { | |
| feedStatus[url] = { status: 'Failed', displayName: url.split('/')[2], lastUpdate: new Date().toISOString() }; | |
| return []; | |
| } | |
| }); | |
| const results = await Promise.allSettled(fetchPromises); | |
| let originalCount = 0; | |
| let filteredCount = 0; | |
| results.forEach(result => { | |
| if (result.status === 'fulfilled') { | |
| originalCount += result.value.length; | |
| const filteredItems = result.value.filter(item => { | |
| const words = item.title.trim().split(/\s+/).filter(w => /[a-zA-Z0-9]/.test(w)); | |
| return words.length >= 5; | |
| }); | |
| filteredCount += filteredItems.length; | |
| allEntries.push(...filteredItems); | |
| } | |
| }); | |
| console.log(`News sync: ${originalCount} total items found, ${filteredCount} passed 5-word filter.`); | |
| const uniqueNews = Array.from(new Map(allEntries.map(item => [item.link, item])).values()); | |
| uniqueNews.sort((a, b) => new Date(b.pubDate) - new Date(a.pubDate)); | |
| cachedNews = uniqueNews; | |
| if (cachedNews.length > 0) { | |
| const avgSentiment = cachedNews.reduce((acc, curr) => acc + curr.sentiment.score, 0) / cachedNews.length; | |
| sentimentTrend.push({ time: new Date().toISOString(), score: avgSentiment }); | |
| if (sentimentTrend.length > 50) sentimentTrend.shift(); | |
| } | |
| if (!fs.existsSync(path.join(__dirname, 'data'))) fs.mkdirSync(path.join(__dirname, 'data')); | |
| try { | |
| await Promise.all([ | |
| fs.promises.writeFile(DATA_FILE, JSON.stringify(cachedNews, null, 2)), | |
| fs.promises.writeFile(TREND_FILE, JSON.stringify(sentimentTrend, null, 2)) | |
| ]); | |
| } catch (err) { | |
| console.error('Error saving data files:', err); | |
| } | |
| isFetching = false; | |
| } | |
| app.get('/api/news', (req, res) => res.json({ news: cachedNews, status: feedStatus, trend: sentimentTrend, indices: cachedIndices })); | |
| app.get('/api/indices', (req, res) => res.json({ indices: cachedIndices })); | |
| cron.schedule('*/5 * * * *', () => fetchAndAnalyzeNews()); | |
| setInterval(fetchIndices, 10000); | |
| if (fs.existsSync(DATA_FILE)) { try { cachedNews = JSON.parse(fs.readFileSync(DATA_FILE)); } catch (e) { } } | |
| if (fs.existsSync(TREND_FILE)) { try { sentimentTrend = JSON.parse(fs.readFileSync(TREND_FILE)); } catch (e) { } } | |
| fetchIndices(); | |
| fetchAndAnalyzeNews(); | |
| app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`)); | |