datamk commited on
Commit
9a5fa10
·
verified ·
1 Parent(s): 8ad151f

Upload 12 files

Browse files
.gitattributes CHANGED
@@ -35,3 +35,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  public/upi-qr.png filter=lfs diff=lfs merge=lfs -text
37
  public/upi-qr.jpeg filter=lfs diff=lfs merge=lfs -text
 
 
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  public/upi-qr.png filter=lfs diff=lfs merge=lfs -text
37
  public/upi-qr.jpeg filter=lfs diff=lfs merge=lfs -text
38
+ fallback.mp4.mp4 filter=lfs diff=lfs merge=lfs -text
Dockerfile CHANGED
Binary files a/Dockerfile and b/Dockerfile differ
 
data/news_sentiment.json CHANGED
The diff for this file is too large to render. See raw diff
 
data/sentiment_trends.json CHANGED
@@ -1 +1,50 @@
1
- [{"time":"2026-04-04T21:41:15.215Z","score":0.65153452685422},{"time":"2026-04-04T21:45:02.202Z","score":0.6615384615384615},{"time":"2026-04-04T21:50:01.769Z","score":0.6424010217113666},{"time":"2026-04-04T21:55:01.624Z","score":0.6520912547528517},{"time":"2026-04-04T21:59:56.833Z","score":0.668806161745828},{"time":"2026-04-04T22:00:01.074Z","score":0.668806161745828},{"time":"2026-04-04T22:05:01.764Z","score":0.6645244215938303},{"time":"2026-04-04T22:05:29.192Z","score":0.659397049390635},{"time":"2026-04-04T22:10:01.872Z","score":0.6662379421221865},{"time":"2026-04-04T22:13:38.928Z","score":0.5837378640776699},{"time":"2026-04-04T22:15:01.507Z","score":0.5837378640776699},{"time":"2026-04-04T22:20:01.546Z","score":0.5888480392156863},{"time":"2026-04-05T00:05:22.157Z","score":0.6219059405940595},{"time":"2026-04-05T00:10:03.695Z","score":0.5962962962962963},{"time":"2026-04-05T00:15:03.835Z","score":0.6608040201005025},{"time":"2026-04-05T00:20:03.920Z","score":0.6275},{"time":"2026-04-05T00:25:04.189Z","score":0.6880503144654088},{"time":"2026-04-05T00:30:03.457Z","score":0.6825297432686287}]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "time": "2026-04-04T21:41:15.215Z",
4
+ "score": 0.65153452685422
5
+ },
6
+ {
7
+ "time": "2026-04-04T21:45:02.202Z",
8
+ "score": 0.6615384615384615
9
+ },
10
+ {
11
+ "time": "2026-04-04T21:50:01.769Z",
12
+ "score": 0.6424010217113666
13
+ },
14
+ {
15
+ "time": "2026-04-04T21:55:01.624Z",
16
+ "score": 0.6520912547528517
17
+ },
18
+ {
19
+ "time": "2026-04-04T21:59:56.833Z",
20
+ "score": 0.668806161745828
21
+ },
22
+ {
23
+ "time": "2026-04-04T22:00:01.074Z",
24
+ "score": 0.668806161745828
25
+ },
26
+ {
27
+ "time": "2026-04-04T22:05:01.764Z",
28
+ "score": 0.6645244215938303
29
+ },
30
+ {
31
+ "time": "2026-04-04T22:05:29.192Z",
32
+ "score": 0.659397049390635
33
+ },
34
+ {
35
+ "time": "2026-04-04T22:10:01.872Z",
36
+ "score": 0.6662379421221865
37
+ },
38
+ {
39
+ "time": "2026-04-04T22:13:38.928Z",
40
+ "score": 0.5837378640776699
41
+ },
42
+ {
43
+ "time": "2026-04-04T22:15:01.507Z",
44
+ "score": 0.5837378640776699
45
+ },
46
+ {
47
+ "time": "2026-04-04T22:20:01.546Z",
48
+ "score": 0.5888480392156863
49
+ }
50
+ ]
fallback.mp4.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:31b57c4c62a54fb71fe2cc6faac4bd501a7d7cf34a8aa4cf8182e69e3f03b174
3
+ size 8679354
index.js CHANGED
@@ -1,37 +1,324 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
36
- public/upi-qr.png filter=lfs diff=lfs merge=lfs -text
37
- public/upi-qr.jpeg filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const axios = require('axios');
3
+ const Parser = require('rss-parser');
4
+ const Sentiment = require('sentiment');
5
+ const cron = require('node-cron');
6
+ const cors = require('cors');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const compression = require('compression');
10
+
11
+ const app = express();
12
+ const parser = new Parser();
13
+ const sentiment = new Sentiment();
14
+ const PORT = 3001;
15
+
16
+ app.use(compression());
17
+ app.use(cors());
18
+ app.use(express.static('public', { maxAge: '1d' }));
19
+
20
+ const DATA_FILE = path.join(__dirname, 'data', 'news_sentiment.json');
21
+ const TREND_FILE = path.join(__dirname, 'data', 'sentiment_trends.json');
22
+
23
+ const RSS_FEEDS = [
24
+ 'https://news.google.com/rss/search?q=when:24h+stock+market+india+NSE+BSE+finance&hl=en-IN&gl=IN&ceid=IN:en',
25
+ 'https://news.google.com/rss/headlines/section/topic/BUSINESS?hl=en-IN&gl=IN&ceid=IN:en',
26
+ 'https://news.google.com/rss/search?q=when:24h+stock+market+india+business&hl=en-IN&gl=IN&ceid=IN:en',
27
+ 'https://news.google.com/rss/search?q=when:24h+site:moneycontrol.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
28
+ 'https://news.google.com/rss/search?q=when:24h+site:financialexpress.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
29
+ 'https://news.google.com/rss/search?q=when:24h+site:business-standard.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
30
+ 'https://economictimes.indiatimes.com/markets/stocks/rssfeeds/2146842.cms',
31
+ 'https://www.livemint.com/rss/markets',
32
+ 'https://news.google.com/rss/search?q=when:24h+site:thehindubusinessline.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
33
+ 'https://news.google.com/rss/search?q=when:24h+site:pulse.zerodha.com&hl=en-IN&gl=IN&ceid=IN:en',
34
+ 'https://news.google.com/rss/search?q=when:24h+site:in.investing.com+intitle:(stocks+OR+markets+OR+india)&hl=en-IN&gl=IN&ceid=IN:en',
35
+ 'https://news.google.com/rss/search?q=when:24h+site:web.stockedge.com&hl=en-IN&gl=IN&ceid=IN:en',
36
+ 'https://news.google.com/rss/search?q=when:24h+site:groww.in&hl=en-IN&gl=IN&ceid=IN:en',
37
+ 'https://news.google.com/rss/search?q=when:24h+site:ndtvprofit.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
38
+ 'https://news.google.com/rss/search?q=when:24h+site:cnbctv18.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
39
+ 'https://news.google.com/rss/search?q=when:24h+site:zeebiz.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
40
+ 'https://news.google.com/rss/search?q=when:24h+site:goodreturns.in+intitle:(stocks+OR+markets+OR+shares)&hl=en-IN&gl=IN&ceid=IN:en',
41
+ 'https://news.google.com/rss/search?q=when:24h+site:ticker.finology.in&hl=en-IN&gl=IN&ceid=IN:en',
42
+ 'https://news.google.com/rss/search?q=when:24h+site:moneyworks4me.com&hl=en-IN&gl=IN&ceid=IN:en',
43
+ 'https://news.google.com/rss/search?q=when:24h+site:trendlyne.com&hl=en-IN&gl=IN&ceid=IN:en',
44
+ 'https://news.google.com/rss/search?q=when:24h+site:tickertape.in&hl=en-IN&gl=IN&ceid=IN:en',
45
+ 'https://news.google.com/rss/search?q=when:24h+site:equitymaster.com+intitle:(stocks+OR+markets+OR+shares)&hl=en-IN&gl=IN&ceid=IN:en',
46
+ 'https://news.google.com/rss/search?q=when:24h+site:marketsmojo.com&hl=en-IN&gl=IN&ceid=IN:en',
47
+ 'https://news.google.com/rss/search?q=when:24h+site:fortuneindia.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
48
+ 'https://news.google.com/rss/search?q=when:24h+site:businessworld.in+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
49
+ 'https://news.google.com/rss/search?q=when:24h+site:outlookbusiness.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
50
+ 'https://news.google.com/rss/search?q=when:24h+site:money.rediff.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
51
+ 'https://news.google.com/rss/search?q=when:24h+site:informistmedia.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
52
+ 'https://news.google.com/rss/search?q=when:24h+site:reuters.com+intitle:(india+AND+(stocks+OR+markets+OR+nse+OR+bse))&hl=en-IN&gl=IN&ceid=IN:en',
53
+ 'https://news.google.com/rss/search?q=when:24h+site:thehindu.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
54
+ 'https://news.google.com/rss/search?q=when:24h+site:timesofindia.indiatimes.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
55
+ 'https://news.google.com/rss/search?q=when:24h+site:deccanherald.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
56
+ 'https://news.google.com/rss/search?q=when:24h+site:bqprime.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
57
+ 'https://news.google.com/rss/search?q=when:24h+site:forbesindia.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
58
+ 'https://news.google.com/rss/search?q=when:24h+site:bloomberg.com+intitle:(india+AND+(stocks+OR+markets+OR+nse+OR+bse))&hl=en-IN&gl=IN&ceid=IN:en',
59
+ 'https://news.google.com/rss/search?q=when:24h+site:dsij.in+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
60
+ 'https://news.google.com/rss/search?q=when:24h+site:economictimes.indiatimes.com/et-now+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
61
+ 'https://news.google.com/rss/search?q=when:24h+site:firstpost.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
62
+ 'https://news.google.com/rss/search?q=when:24h+site:capitalmarket.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
63
+ 'https://news.google.com/rss/search?q=when:24h+site:news18.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
64
+ 'https://news.google.com/rss/search?q=when:24h+site:news.abplive.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
65
+ 'https://news.google.com/rss/search?q=when:24h+site:mydigitalfc.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
66
+ 'https://news.google.com/rss/search?q=when:24h+site:asia.nikkei.com+intitle:(india+AND+(stocks+OR+markets+OR+nse+OR+bse))&hl=en-IN&gl=IN&ceid=IN:en',
67
+ 'https://news.google.com/rss/search?q=when:24h+site:icicidirect.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
68
+ 'https://news.google.com/rss/search?q=when:24h+site:hdfcsec.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
69
+ 'https://news.google.com/rss/search?q=when:24h+site:smallcase.com+intitle:(stocks+OR+markets+OR+investing)&hl=en-IN&gl=IN&ceid=IN:en',
70
+ 'https://news.google.com/rss/search?q=when:24h+site:upstox.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
71
+ 'https://news.google.com/rss/search?q=when:24h+site:angelone.in+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
72
+ 'https://news.google.com/rss/search?q=when:24h+site:motilaloswal.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
73
+ 'https://news.google.com/rss/search?q=when:24h+site:finshots.in+intitle:(stocks+OR+markets+OR+shares)&hl=en-IN&gl=IN&ceid=IN:en',
74
+ 'https://news.google.com/rss/search?q=when:24h+site:tradebrains.in+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
75
+ 'https://news.google.com/rss/search?q=when:24h+site:valueresearchonline.com+intitle:(stocks+OR+markets+OR+shares)&hl=en-IN&gl=IN&ceid=IN:en',
76
+ 'https://news.google.com/rss/search?q=when:24h+site:entrackr.com+intitle:(stocks+OR+markets+OR+ipo+OR+listed)&hl=en-IN&gl=IN&ceid=IN:en',
77
+ 'https://news.google.com/rss/search?q=when:24h+site:inc42.com+intitle:(stocks+OR+markets+OR+ipo+OR+listed)&hl=en-IN&gl=IN&ceid=IN:en',
78
+ 'https://news.google.com/rss/search?q=when:24h+site:ft.com+intitle:(india+AND+(stocks+OR+markets+OR+nse+OR+bse))&hl=en-IN&gl=IN&ceid=IN:en',
79
+ 'https://news.google.com/rss/search?q=when:24h+site:economist.com+intitle:(india+AND+(business+OR+markets+OR+economy))&hl=en-IN&gl=IN&ceid=IN:en',
80
+ 'https://news.google.com/rss/search?q=when:24h+site:wsj.com+intitle:(india+AND+(stocks+OR+markets+OR+corporate))&hl=en-IN&gl=IN&ceid=IN:en',
81
+ 'https://news.google.com/rss/search?q=when:24h+site:thestatesman.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
82
+ 'https://news.google.com/rss/search?q=when:24h+site:dailypioneer.com+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
83
+ 'https://news.google.com/rss/search?q=when:24h+site:freepressjournal.in+intitle:(stocks+OR+markets+OR+nse+OR+bse)&hl=en-IN&gl=IN&ceid=IN:en',
84
+ 'https://www.businesstoday.in/rss/markets',
85
+ 'https://news.google.com/rss/search?q=when:24h+site:screener.in&hl=en-IN&gl=IN&ceid=IN:en',
86
+ 'https://news.google.com/rss/search?q=when:24h+site:capitalmind.in&hl=en-IN&gl=IN&ceid=IN:en',
87
+ 'https://news.google.com/rss/search?q=when:24h+site:thecore.in&hl=en-IN&gl=IN&ceid=IN:en',
88
+ 'https://news.google.com/rss/search?q=when:24h+site:bseindia.com+corporate+announcements&hl=en-IN&gl=IN&ceid=IN:en',
89
+ 'https://news.google.com/rss/search?q=when:24h+site:nseindia.com+corporate+announcements&hl=en-IN&gl=IN&ceid=IN:en',
90
+ 'https://news.google.com/rss/search?q=when:24h+site:alphastreet.com+india&hl=en-IN&gl=IN&ceid=IN:en',
91
+ 'https://news.google.com/rss/search?q=when:24h+intitle:(dividend+OR+buyback+OR+"bonus+issue"+OR+"stock+split")+NSE+BSE&hl=en-IN&gl=IN&ceid=IN:en',
92
+ 'https://news.google.com/rss/search?q=when:24h+intitle:("quarterly+results"+OR+"earnings"+OR+"PAT+growth")+NSE+BSE&hl=en-IN&gl=IN&ceid=IN:en',
93
+ 'https://news.google.com/rss/search?q=when:24h+intitle:("FII+buying"+OR+"DII+selling"+OR+"bulk+deal"+OR+"block+deal")+NSE+BSE&hl=en-IN&gl=IN&ceid=IN:en',
94
+ 'https://news.google.com/rss/search?q=when:24h+intitle:("SEBI"+OR+"RBI+monetary+policy"+OR+"repo+rate")+India&hl=en-IN&gl=IN&ceid=IN:en',
95
+ 'https://news.google.com/rss/search?q=when:24h+intitle:("52-week+high"+OR+"breakout"+OR+"upper+circuit")+NSE+BSE&hl=en-IN&gl=IN&ceid=IN:en',
96
+ 'https://news.google.com/rss/search?q=when:24h+intitle:("IPO+allotment"+OR+"IPO+listing"+OR+"Grey+Market+Premium")+India&hl=en-IN&gl=IN&ceid=IN:en',
97
+ 'https://news.google.com/rss/search?q=when:24h+intitle:("Auto+sales"+OR+"USFDA")+India&hl=en-IN&gl=IN&ceid=IN:en',
98
+ 'https://news.google.com/rss/search?q=when:24h+intitle:("Management+change"+OR+"CEO+resignation"+OR+"CFO+appointment")+India&hl=en-IN&gl=IN&ceid=IN:en'
99
+ ];
100
+
101
+
102
+
103
+
104
+ const BROWSER_HEADERS = {
105
+ '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',
106
+ 'Accept': 'application/rss+xml,application/xml;q=0.9,*/*;q=0.8',
107
+ 'Accept-Language': 'en-US,en;q=0.9'
108
+ };
109
+
110
+ let cachedNews = [];
111
+ let feedStatus = {};
112
+ let sentimentTrend = [];
113
+ let cachedIndices = {};
114
+ let isFetching = false;
115
+
116
+ function getSourceName(url, feedTitle) {
117
+ if (url.includes('NSE+BSE+finance')) return 'Google Finance (Markets)';
118
+ if (url.includes('site:moneycontrol.com')) return 'Moneycontrol';
119
+ if (url.includes('site:financialexpress.com')) return 'Financial Express';
120
+ if (url.includes('site:business-standard.com')) return 'Business Standard';
121
+ if (url.includes('economictimes')) return 'Economic Times';
122
+ if (url.includes('livemint')) return 'LiveMint';
123
+ if (url.includes('headlines/section/topic/BUSINESS')) return 'Business News';
124
+ if (url.includes('site:thehindubusinessline.com')) return 'The Hindu Business Line';
125
+ if (url.includes('site:pulse.zerodha.com')) return 'Zerodha Pulse';
126
+ if (url.includes('site:in.investing.com')) return 'Investing.com';
127
+ if (url.includes('site:web.stockedge.com')) return 'StockEdge';
128
+ if (url.includes('site:groww.in')) return 'Groww';
129
+ if (url.includes('site:ndtvprofit.com')) return 'NDTV Profit';
130
+ if (url.includes('site:cnbctv18.com')) return 'CNBC TV18';
131
+ if (url.includes('site:zeebiz.com')) return 'Zee Business';
132
+ if (url.includes('site:goodreturns.in')) return 'GoodReturns';
133
+ if (url.includes('site:ticker.finology.in')) return 'Finology Ticker';
134
+ if (url.includes('site:moneyworks4me.com')) return 'MoneyWorks4Me';
135
+ if (url.includes('site:trendlyne.com')) return 'Trendlyne';
136
+ if (url.includes('site:tickertape.in')) return 'Tickertape';
137
+ if (url.includes('site:equitymaster.com')) return 'Equitymaster';
138
+ if (url.includes('site:marketsmojo.com')) return 'MarketsMojo';
139
+ if (url.includes('site:fortuneindia.com')) return 'Fortune India';
140
+ if (url.includes('site:businessworld.in')) return 'BusinessWorld';
141
+ if (url.includes('site:outlookbusiness.com')) return 'Outlook Business';
142
+ if (url.includes('site:money.rediff.com')) return 'Rediff Money';
143
+ if (url.includes('site:informistmedia.com')) return 'Informist Media';
144
+ if (url.includes('site:reuters.com')) return 'Reuters India';
145
+ if (url.includes('site:thehindu.com')) return 'The Hindu';
146
+ if (url.includes('site:timesofindia.indiatimes.com')) return 'Times of India';
147
+ if (url.includes('site:deccanherald.com')) return 'Deccan Herald';
148
+ if (url.includes('site:bqprime.com')) return 'BQ Prime';
149
+ if (url.includes('site:forbesindia.com')) return 'Forbes India';
150
+ if (url.includes('site:bloomberg.com')) return 'Bloomberg India';
151
+ if (url.includes('site:dsij.in')) return 'DSIJ';
152
+ if (url.includes('site:economictimes.indiatimes.com/et-now')) return 'ET Now';
153
+ if (url.includes('site:firstpost.com')) return 'Firstpost Business';
154
+ if (url.includes('site:capitalmarket.com')) return 'Capital Market';
155
+ if (url.includes('site:news18.com')) return 'News18 Business';
156
+ if (url.includes('site:news.abplive.com')) return 'ABP Business';
157
+ if (url.includes('site:mydigitalfc.com')) return 'Financial Chronicle';
158
+ if (url.includes('site:asia.nikkei.com')) return 'Nikkei Asia (India)';
159
+ if (url.includes('site:icicidirect.com')) return 'ICICIDirect Research';
160
+ if (url.includes('site:hdfcsec.com')) return 'HDFC Securities';
161
+ if (url.includes('site:smallcase.com')) return 'Smallcase Insights';
162
+ if (url.includes('site:upstox.com')) return 'Upstox Updates';
163
+ if (url.includes('site:angelone.in')) return 'Angel One';
164
+ if (url.includes('businesstoday')) return 'Business Today';
165
+ if (url.includes('site:screener.in')) return 'Screener.in';
166
+ if (url.includes('site:capitalmind.in')) return 'CapitalMind';
167
+ if (url.includes('site:thecore.in')) return 'The Core';
168
+ if (url.includes('site:bseindia.com')) return 'BSE Announcements';
169
+ if (url.includes('site:nseindia.com')) return 'NSE Announcements';
170
+ if (url.includes('site:alphastreet.com')) return 'AlphaStreet India';
171
+ if (url.includes('intitle:(dividend')) return 'Corporate Action (Dividend/Buyback)';
172
+ if (url.includes('intitle:("quarterly+results"')) return 'Earnings Alert';
173
+ if (url.includes('intitle:("FII+buying"')) return 'Institutional Flow Alert';
174
+ if (url.includes('intitle:("SEBI"')) return 'Regulatory/Policy Alert';
175
+ if (url.includes('intitle:("52-week+high"')) return 'Momentum/Breakout Alert';
176
+ if (url.includes('intitle:("IPO+allotment"')) return 'IPO/Listing Alert';
177
+ if (url.includes('intitle:("Auto+sales"')) return 'Sector Update';
178
+ if (url.includes('intitle:("Management+change"')) return 'Management Update';
179
+ if (url.includes('site:motilaloswal.com')) return 'Motilal Oswal';
180
+ if (url.includes('site:finshots.in')) return 'Finshots';
181
+ if (url.includes('site:tradebrains.in')) return 'Trade Brains';
182
+ if (url.includes('site:valueresearchonline.com')) return 'Value Research';
183
+ if (url.includes('site:entrackr.com')) return 'Entrackr (Tech Stocks)';
184
+ if (url.includes('site:inc42.com')) return 'Inc42 (Tech Stocks)';
185
+ if (url.includes('site:ft.com')) return 'Financial Times (India)';
186
+ if (url.includes('site:economist.com')) return 'The Economist (India)';
187
+ if (url.includes('site:wsj.com')) return 'WSJ (India)';
188
+ if (url.includes('site:thestatesman.com')) return 'The Statesman';
189
+ if (url.includes('site:dailypioneer.com')) return 'The Pioneer';
190
+ if (url.includes('site:freepressjournal.in')) return 'Free Press Journal';
191
+ return feedTitle?.replace('Google News - ', '') || 'Market News';
192
+ }
193
+
194
+ async function fetchIndices() {
195
+ const tickers = {
196
+ 'NIFTY 50': '^NSEI',
197
+ 'BANK NIFTY': '^NSEBANK',
198
+ 'SENSEX': '^BSESN',
199
+ 'FINNIFTY': 'NIFTY_FIN_SERVICE.NS',
200
+ 'INDIA VIX': '^INDIAVIX',
201
+ 'USD/INR': 'INR=X'
202
+ };
203
+
204
+ const tickerEntries = Object.entries(tickers);
205
+
206
+ // Fetch in parallel but keep the order
207
+ const responses = await Promise.allSettled(tickerEntries.map(async ([name, ticker]) => {
208
+ const response = await axios.get(`https://query1.finance.yahoo.com/v8/finance/chart/${ticker}?interval=1m&range=1d`, {
209
+ headers: { 'User-Agent': BROWSER_HEADERS['User-Agent'] },
210
+ timeout: 5000
211
+ });
212
+ const meta = response.data.chart.result[0].meta;
213
+ return {
214
+ price: meta.regularMarketPrice.toFixed(2),
215
+ change: (meta.regularMarketPrice - meta.previousClose).toFixed(2),
216
+ changePercent: (((meta.regularMarketPrice - meta.previousClose) / meta.previousClose) * 100).toFixed(2)
217
+ };
218
+ }));
219
+
220
+ const results = {};
221
+ tickerEntries.forEach(([name], index) => {
222
+ const res = responses[index];
223
+ if (res.status === 'fulfilled') {
224
+ results[name] = res.value;
225
+ } else {
226
+ results[name] = cachedIndices[name] || { price: '---', change: '0', changePercent: '0' };
227
+ }
228
+ });
229
+
230
+ cachedIndices = results;
231
+ }
232
+
233
+ async function fetchAndAnalyzeNews() {
234
+ if (isFetching) return;
235
+ isFetching = true;
236
+ const allEntries = [];
237
+ const now = new Date();
238
+ const twentyFourHoursAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
239
+
240
+ const fetchPromises = RSS_FEEDS.map(async (url) => {
241
+ try {
242
+ const response = await axios.get(url, { headers: BROWSER_HEADERS, timeout: 10000 });
243
+ const feed = await parser.parseString(response.data);
244
+ const sourceName = getSourceName(url, feed.title);
245
+ feedStatus[url] = { status: 'Success', displayName: sourceName, lastUpdate: new Date().toISOString() };
246
+
247
+ const items = [];
248
+ feed.items.forEach(item => {
249
+ const pubDate = new Date(item.pubDate);
250
+ if (pubDate >= twentyFourHoursAgo) {
251
+ const fullText = `${item.title} ${item.contentSnippet || ''}`;
252
+ const analysis = sentiment.analyze(fullText);
253
+ items.push({
254
+ id: item.guid || item.link,
255
+ title: item.title,
256
+ link: item.link,
257
+ pubDate: item.pubDate,
258
+ source: sourceName,
259
+ sentiment: {
260
+ score: analysis.score,
261
+ label: analysis.score > 0 ? 'Positive' : (analysis.score < 0 ? 'Negative' : 'Neutral')
262
+ }
263
+ });
264
+ }
265
+ });
266
+ return items;
267
+ } catch (e) {
268
+ feedStatus[url] = { status: 'Failed', displayName: url.split('/')[2], lastUpdate: new Date().toISOString() };
269
+ return [];
270
+ }
271
+ });
272
+
273
+ const results = await Promise.allSettled(fetchPromises);
274
+ let originalCount = 0;
275
+ let filteredCount = 0;
276
+
277
+ results.forEach(result => {
278
+ if (result.status === 'fulfilled') {
279
+ originalCount += result.value.length;
280
+ const filteredItems = result.value.filter(item => {
281
+ const words = item.title.trim().split(/\s+/).filter(w => /[a-zA-Z0-9]/.test(w));
282
+ return words.length >= 5;
283
+ });
284
+ filteredCount += filteredItems.length;
285
+ allEntries.push(...filteredItems);
286
+ }
287
+ });
288
+ console.log(`News sync: ${originalCount} total items found, ${filteredCount} passed 5-word filter.`);
289
+
290
+ const uniqueNews = Array.from(new Map(allEntries.map(item => [item.link, item])).values());
291
+ uniqueNews.sort((a, b) => new Date(b.pubDate) - new Date(a.pubDate));
292
+ cachedNews = uniqueNews;
293
+
294
+ if (cachedNews.length > 0) {
295
+ const avgSentiment = cachedNews.reduce((acc, curr) => acc + curr.sentiment.score, 0) / cachedNews.length;
296
+ sentimentTrend.push({ time: new Date().toISOString(), score: avgSentiment });
297
+ if (sentimentTrend.length > 50) sentimentTrend.shift();
298
+ }
299
+
300
+ if (!fs.existsSync(path.join(__dirname, 'data'))) fs.mkdirSync(path.join(__dirname, 'data'));
301
+ try {
302
+ await Promise.all([
303
+ fs.promises.writeFile(DATA_FILE, JSON.stringify(cachedNews, null, 2)),
304
+ fs.promises.writeFile(TREND_FILE, JSON.stringify(sentimentTrend, null, 2))
305
+ ]);
306
+ } catch (err) {
307
+ console.error('Error saving data files:', err);
308
+ }
309
+ isFetching = false;
310
+ }
311
+
312
+ app.get('/api/news', (req, res) => res.json({ news: cachedNews, status: feedStatus, trend: sentimentTrend, indices: cachedIndices }));
313
+ app.get('/api/indices', (req, res) => res.json({ indices: cachedIndices }));
314
+
315
+ cron.schedule('*/5 * * * *', () => fetchAndAnalyzeNews());
316
+ setInterval(fetchIndices, 10000);
317
+
318
+ if (fs.existsSync(DATA_FILE)) { try { cachedNews = JSON.parse(fs.readFileSync(DATA_FILE)); } catch (e) { } }
319
+ if (fs.existsSync(TREND_FILE)) { try { sentimentTrend = JSON.parse(fs.readFileSync(TREND_FILE)); } catch (e) { } }
320
+
321
+ fetchIndices();
322
+ fetchAndAnalyzeNews();
323
+
324
+ app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));
package.json CHANGED
@@ -1,17 +1,22 @@
1
- FROM node:20-slim
2
-
3
- WORKDIR /app
4
-
5
- COPY package*.json ./
6
- RUN npm install --production
7
-
8
- COPY . .
9
-
10
- # Create data directory if it doesn't exist
11
- RUN mkdir -p data && chown -R node:node /app
12
-
13
- USER node
14
-
15
- EXPOSE 3001
16
-
17
- CMD ["node", "index.js"]
 
 
 
 
 
 
1
+ {
2
+ "name": "stock-sentiment-dashboard",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC",
12
+ "type": "commonjs",
13
+ "dependencies": {
14
+ "axios": "^1.13.5",
15
+ "compression": "^1.8.1",
16
+ "cors": "^2.8.6",
17
+ "express": "^5.2.1",
18
+ "node-cron": "^4.2.1",
19
+ "rss-parser": "^3.13.0",
20
+ "sentiment": "^5.0.2"
21
+ }
22
+ }
public/app.js CHANGED
@@ -5,8 +5,6 @@ let currentSource = 'all';
5
  let searchQuery = '';
6
  let renderedCount = 0;
7
  let currentRenderedFiltered = [];
8
- let lastUpdateTimestamp = 0;
9
- let isPageVisible = true;
10
 
11
  const IMPACT_KEYWORDS = {
12
  high: ['crash', 'crisis', 'urgent', 'breaking', 'collapsed', 'warns', 'surge', 'plunge', 'lockdown', 'rating', 'result', 'focus'],
@@ -21,68 +19,25 @@ const IMPACT_REGEX = Object.fromEntries(
21
  ])
22
  );
23
 
24
- const highlightCache = new Map();
25
-
26
- function loadFromCache() {
27
- try {
28
- const cached = localStorage.getItem('trading_pulse_cache');
29
- if (cached) {
30
- const data = JSON.parse(cached);
31
- allNews = data.news || [];
32
- sentimentTrend = data.trend || [];
33
- if (allNews.length > 0) {
34
- renderDashboard();
35
- if (data.indices) renderIndices(data.indices);
36
- }
37
- }
38
- } catch (e) {
39
- console.warn('Failed to load from cache', e);
40
- }
41
- }
42
-
43
- function saveToCache(data) {
44
- try {
45
- localStorage.setItem('trading_pulse_cache', JSON.stringify(data));
46
- } catch (e) {
47
- console.warn('Failed to save to cache', e);
48
- }
49
- }
50
-
51
  async function fetchNews() {
52
  try {
53
- const response = await fetch(`/api/news?t=${Date.now()}`);
54
  const data = await response.json();
55
-
56
- lastUpdateTimestamp = Date.now();
57
- updateSyncTime();
58
-
59
- // Fast deep equality check to skip re-render if data hasn't changed
60
- const dataStr = JSON.stringify(data);
61
- if (localStorage.getItem('trading_pulse_cache') === dataStr) return;
62
-
63
  allNews = data.news;
64
  sentimentTrend = data.trend;
65
-
66
- saveToCache(data);
67
  renderDashboard();
68
  renderIndices(data.indices);
69
  } catch (error) {
70
  console.error('Error fetching news:', error);
71
- if (allNews.length === 0) {
72
- const feed = document.getElementById('news-feed');
73
- if (feed) feed.innerHTML = '<div class="loader">Market data sync failed. Check connection.</div>';
74
- }
75
  }
76
  }
77
 
78
- function updateSyncTime() {
79
- const el = document.getElementById('last-updated');
80
- if (el) el.textContent = `Synced: ${new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })}`;
81
- }
82
-
83
  async function fetchIndicesOnly() {
84
  try {
85
- const response = await fetch(`/api/indices?t=${Date.now()}`);
86
  const data = await response.json();
87
  renderIndices(data.indices);
88
  } catch (error) {
@@ -196,12 +151,10 @@ function renderIndices(indices) {
196
  }
197
 
198
  function highlightImpact(title) {
199
- if (highlightCache.has(title)) return highlightCache.get(title);
200
  let result = title;
201
  Object.entries(IMPACT_REGEX).forEach(([type, regex]) => {
202
  result = result.replace(regex, `<span class="impact-word impact-${type}">$1</span>`);
203
  });
204
- highlightCache.set(title, result);
205
  return result;
206
  }
207
 
@@ -253,18 +206,6 @@ window.addEventListener('popstate', function (e) {
253
  });
254
 
255
 
256
- document.addEventListener('visibilitychange', () => {
257
- isPageVisible = !document.hidden;
258
- if (isPageVisible) {
259
- // Refresh immediately if data is more than 30s old
260
- if (Date.now() - lastUpdateTimestamp > 30000) {
261
- fetchNews();
262
- fetchIndicesOnly();
263
- }
264
- }
265
- });
266
-
267
- loadFromCache();
268
  fetchNews();
269
- setInterval(() => { if (isPageVisible) fetchNews(); }, 30000);
270
- setInterval(() => { if (isPageVisible) fetchIndicesOnly(); }, 10000);
 
5
  let searchQuery = '';
6
  let renderedCount = 0;
7
  let currentRenderedFiltered = [];
 
 
8
 
9
  const IMPACT_KEYWORDS = {
10
  high: ['crash', 'crisis', 'urgent', 'breaking', 'collapsed', 'warns', 'surge', 'plunge', 'lockdown', 'rating', 'result', 'focus'],
 
19
  ])
20
  );
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  async function fetchNews() {
23
  try {
24
+ const response = await fetch('/api/news');
25
  const data = await response.json();
 
 
 
 
 
 
 
 
26
  allNews = data.news;
27
  sentimentTrend = data.trend;
28
+
 
29
  renderDashboard();
30
  renderIndices(data.indices);
31
  } catch (error) {
32
  console.error('Error fetching news:', error);
33
+ const feed = document.getElementById('news-feed');
34
+ if (feed) feed.innerHTML = '<div class="loader">Market data sync failed. Check connection.</div>';
 
 
35
  }
36
  }
37
 
 
 
 
 
 
38
  async function fetchIndicesOnly() {
39
  try {
40
+ const response = await fetch('/api/indices');
41
  const data = await response.json();
42
  renderIndices(data.indices);
43
  } catch (error) {
 
151
  }
152
 
153
  function highlightImpact(title) {
 
154
  let result = title;
155
  Object.entries(IMPACT_REGEX).forEach(([type, regex]) => {
156
  result = result.replace(regex, `<span class="impact-word impact-${type}">$1</span>`);
157
  });
 
158
  return result;
159
  }
160
 
 
206
  });
207
 
208
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  fetchNews();
210
+ setInterval(fetchNews, 30000);
211
+ setInterval(fetchIndicesOnly, 10000);
public/index.html CHANGED
@@ -29,11 +29,8 @@
29
 
30
  <div class="stats">
31
  <div class="live-status">
32
- <div class="status-indicator">
33
- <span class="live-pulse"></span>
34
- <span>LIVE</span>
35
- </div>
36
- <span id="last-updated" class="last-sync">Syncing...</span>
37
  </div>
38
  </div>
39
  </header>
 
29
 
30
  <div class="stats">
31
  <div class="live-status">
32
+ <span class="live-pulse"></span>
33
+ <span>LIVE</span>
 
 
 
34
  </div>
35
  </div>
36
  </header>