datamk commited on
Commit
5dcfd2c
·
verified ·
1 Parent(s): 0aea15a

Upload 9 files

Browse files
Files changed (7) hide show
  1. Dockerfile +0 -0
  2. app.js +50 -0
  3. download +574 -0
  4. index.js +37 -372
  5. sentiment_trends.json +0 -0
  6. style.css +211 -0
  7. upi-qr.jpeg +77 -0
Dockerfile CHANGED
Binary files a/Dockerfile and b/Dockerfile differ
 
app.js ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ ]
download ADDED
@@ -0,0 +1,574 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --bg-dark: #0a0b10;
3
+ --card-bg: rgba(18, 20, 30, 0.7);
4
+ --card-border: rgba(255, 255, 255, 0.08);
5
+ --text-primary: #e0e6ed;
6
+ --text-secondary: #94a3b8;
7
+ --accent-blue: #52afff;
8
+ --accent-gold: #ffd740;
9
+ --accent-green: #00e676;
10
+ --accent-red: #ff5252;
11
+ --impact-high: #ff9800;
12
+ }
13
+
14
+ * {
15
+ margin: 0;
16
+ padding: 0;
17
+ box-sizing: border-box;
18
+ scrollbar-width: none;
19
+ }
20
+
21
+ *::-webkit-scrollbar {
22
+ display: none;
23
+ }
24
+
25
+ body {
26
+ background-color: var(--bg-dark);
27
+ color: var(--text-primary);
28
+ font-family: 'Outfit', sans-serif;
29
+ line-height: 1.6;
30
+ overflow-x: hidden;
31
+ }
32
+
33
+ .background-blobs {
34
+ position: fixed;
35
+ top: 0;
36
+ left: 0;
37
+ width: 100%;
38
+ height: 100%;
39
+ z-index: -1;
40
+ filter: blur(80px);
41
+ }
42
+
43
+ .blob {
44
+ position: absolute;
45
+ border-radius: 50%;
46
+ opacity: 0.15;
47
+ }
48
+
49
+ .blob-1 {
50
+ top: -10%;
51
+ left: -10%;
52
+ width: 40%;
53
+ height: 40%;
54
+ background: var(--accent-blue);
55
+ }
56
+
57
+ .blob-2 {
58
+ bottom: -10%;
59
+ right: -10%;
60
+ width: 40%;
61
+ height: 40%;
62
+ background: var(--accent-red);
63
+ }
64
+
65
+ .blob-3 {
66
+ top: 40%;
67
+ left: 40%;
68
+ width: 30%;
69
+ height: 30%;
70
+ background: var(--accent-gold);
71
+ }
72
+
73
+ .dashboard {
74
+ max-width: 100%;
75
+ margin: 0;
76
+ padding: 0.5rem 1rem;
77
+ }
78
+
79
+ .header {
80
+ display: flex;
81
+ justify-content: space-between;
82
+ align-items: center;
83
+ margin-bottom: 0.5rem;
84
+ padding: 0.5rem 0;
85
+ border-bottom: 1px solid var(--card-border);
86
+ gap: 1rem;
87
+ }
88
+
89
+ .logo {
90
+ display: flex;
91
+ align-items: center;
92
+ gap: 0.75rem;
93
+ flex-shrink: 0;
94
+ }
95
+
96
+ .logo h1 {
97
+ font-size: 1.2rem;
98
+ font-weight: 700;
99
+ letter-spacing: -0.5px;
100
+ }
101
+
102
+ .index-ticker {
103
+ flex: 1;
104
+ display: flex;
105
+ flex-wrap: wrap;
106
+ gap: 0.4rem 0.75rem;
107
+ padding: 0 0.5rem;
108
+ justify-content: space-evenly;
109
+ align-items: center;
110
+ }
111
+
112
+ .ticker-item {
113
+ display: flex;
114
+ flex-direction: column;
115
+ flex-shrink: 1;
116
+ min-width: 0;
117
+ }
118
+
119
+ .ticker-name {
120
+ font-size: 0.55rem;
121
+ font-weight: 700;
122
+ color: var(--text-secondary);
123
+ text-transform: uppercase;
124
+ letter-spacing: 0.3px;
125
+ margin-bottom: -2px;
126
+ white-space: nowrap;
127
+ }
128
+
129
+ .ticker-data {
130
+ display: flex;
131
+ align-items: center;
132
+ gap: 0.25rem;
133
+ white-space: nowrap;
134
+ }
135
+
136
+ .ticker-price {
137
+ font-size: 0.8rem;
138
+ font-weight: 600;
139
+ }
140
+
141
+ .ticker-change {
142
+ font-size: 0.65rem;
143
+ font-weight: 600;
144
+ }
145
+
146
+ .ticker-change.up {
147
+ color: var(--accent-green);
148
+ }
149
+
150
+ .ticker-change.down {
151
+ color: var(--accent-red);
152
+ }
153
+
154
+ @media (max-width: 480px) {
155
+ .header {
156
+ gap: 0.5rem;
157
+ padding: 0.4rem 0;
158
+ }
159
+
160
+ .index-ticker {
161
+ gap: 0.3rem 0.5rem;
162
+ padding: 0 0.25rem;
163
+ }
164
+
165
+ .ticker-name {
166
+ font-size: 0.48rem;
167
+ letter-spacing: 0;
168
+ }
169
+
170
+ .ticker-price {
171
+ font-size: 0.68rem;
172
+ }
173
+
174
+ .ticker-change {
175
+ font-size: 0.55rem;
176
+ }
177
+ }
178
+
179
+ @media (max-width: 360px) {
180
+ .index-ticker {
181
+ gap: 0.2rem 0.4rem;
182
+ padding: 0;
183
+ }
184
+
185
+ .ticker-name {
186
+ font-size: 0.44rem;
187
+ }
188
+
189
+ .ticker-price {
190
+ font-size: 0.6rem;
191
+ }
192
+
193
+ .ticker-change {
194
+ font-size: 0.5rem;
195
+ }
196
+ }
197
+
198
+ .stats {
199
+ display: flex;
200
+ align-items: center;
201
+ gap: 1rem;
202
+ flex-shrink: 0;
203
+ }
204
+
205
+
206
+
207
+ .live-status {
208
+ display: flex;
209
+ align-items: center;
210
+ gap: 0.5rem;
211
+ font-size: 0.65rem;
212
+ font-weight: 700;
213
+ color: var(--accent-green);
214
+ }
215
+
216
+ .live-pulse {
217
+ width: 6px;
218
+ height: 6px;
219
+ background: var(--accent-green);
220
+ border-radius: 50%;
221
+ box-shadow: 0 0 8px var(--accent-green);
222
+ animation: pulse 2s infinite;
223
+ }
224
+
225
+ @keyframes pulse {
226
+ 0% {
227
+ opacity: 0.4;
228
+ }
229
+
230
+ 50% {
231
+ opacity: 1;
232
+ }
233
+
234
+ 100% {
235
+ opacity: 0.4;
236
+ }
237
+ }
238
+
239
+ .content-grid {
240
+ display: block;
241
+ }
242
+
243
+ .card {
244
+ background: var(--card-bg);
245
+ border: 1px solid var(--card-border);
246
+ border-radius: 12px;
247
+ padding: 1rem;
248
+ }
249
+
250
+
251
+
252
+ .feed-header {
253
+ margin-bottom: 1rem;
254
+ }
255
+
256
+ .header-main {
257
+ display: flex;
258
+ align-items: center;
259
+ justify-content: space-between;
260
+ margin-bottom: 0.75rem;
261
+ }
262
+
263
+ .header-main h2 {
264
+ font-size: 1.1rem;
265
+ }
266
+
267
+ .filter-controls {
268
+ display: flex;
269
+ gap: 0.5rem;
270
+ }
271
+
272
+ .filter-btn {
273
+ background: transparent;
274
+ border: 1px solid var(--card-border);
275
+ color: var(--text-secondary);
276
+ padding: 4px 12px;
277
+ border-radius: 20px;
278
+ cursor: pointer;
279
+ font-size: 0.7rem;
280
+ }
281
+
282
+ .filter-btn.active {
283
+ background: var(--text-secondary);
284
+ color: var(--bg-dark);
285
+ }
286
+
287
+ .search-container {
288
+ flex: 1;
289
+ max-width: 300px;
290
+ margin: 0 1rem;
291
+ }
292
+
293
+ #news-search {
294
+ width: 100%;
295
+ background: var(--card-bg);
296
+ border: 1px solid var(--card-border);
297
+ color: var(--text-primary);
298
+ padding: 6px 12px;
299
+ border-radius: 20px;
300
+ font-size: 0.8rem;
301
+ font-family: inherit;
302
+ outline: none;
303
+ transition: border-color 0.2s;
304
+ }
305
+
306
+ #news-search:focus {
307
+ border-color: var(--accent-blue);
308
+ }
309
+
310
+ @media (max-width: 600px) {
311
+ .header-main {
312
+ flex-wrap: wrap;
313
+ gap: 0.75rem;
314
+ }
315
+ .search-container {
316
+ order: 3;
317
+ max-width: 100%;
318
+ margin: 0;
319
+ width: 100%;
320
+ }
321
+ }
322
+
323
+ .feed-section {
324
+ min-width: 0;
325
+ overflow: hidden;
326
+ }
327
+
328
+ .news-feed {
329
+ min-width: 0;
330
+ }
331
+
332
+ .news-article {
333
+ border-bottom: 1px solid var(--card-border);
334
+ padding: 0.75rem 0.25rem;
335
+ animation: fadeIn 0.4s ease backwards;
336
+ overflow-wrap: break-word;
337
+ word-wrap: break-word;
338
+ min-width: 0;
339
+ }
340
+
341
+ @keyframes fadeIn {
342
+ from {
343
+ opacity: 0;
344
+ transform: translateY(10px);
345
+ }
346
+
347
+ to {
348
+ opacity: 1;
349
+ transform: translateY(0);
350
+ }
351
+ }
352
+
353
+ .article-header {
354
+ display: flex;
355
+ flex-wrap: wrap;
356
+ align-items: center;
357
+ gap: 0.25rem;
358
+ }
359
+
360
+ .source {
361
+ color: var(--accent-blue);
362
+ font-weight: 700;
363
+ font-size: 0.7rem;
364
+ margin-right: 0.5rem;
365
+ }
366
+
367
+ .time {
368
+ color: var(--text-secondary);
369
+ font-size: 0.7rem;
370
+ }
371
+
372
+ .news-article h3 {
373
+ font-size: 0.95rem;
374
+ line-height: 1.4;
375
+ font-weight: 500;
376
+ overflow-wrap: break-word;
377
+ word-wrap: break-word;
378
+ hyphens: auto;
379
+ }
380
+
381
+ @media (max-width: 480px) {
382
+ .news-article {
383
+ padding: 0.6rem 0.15rem;
384
+ }
385
+
386
+ .news-article h3 {
387
+ font-size: 0.85rem;
388
+ line-height: 1.35;
389
+ }
390
+
391
+ .source {
392
+ font-size: 0.6rem;
393
+ }
394
+
395
+ .time {
396
+ font-size: 0.6rem;
397
+ }
398
+
399
+ .feed-header {
400
+ margin-bottom: 0.5rem;
401
+ }
402
+
403
+ .header-main h2 {
404
+ font-size: 0.95rem;
405
+ }
406
+
407
+ .filter-btn {
408
+ padding: 3px 8px;
409
+ font-size: 0.6rem;
410
+ }
411
+ }
412
+
413
+ .impact-word {
414
+ font-weight: 700;
415
+ padding: 0 4px;
416
+ border-radius: 4px;
417
+ }
418
+
419
+ .impact-high {
420
+ color: #ffeb3b;
421
+ background: rgba(255, 235, 59, 0.15);
422
+ }
423
+
424
+ .impact-pos {
425
+ color: #00e676;
426
+ background: rgba(0, 230, 118, 0.1);
427
+ }
428
+
429
+ .impact-neg {
430
+ color: #ff5252;
431
+ background: rgba(255, 82, 82, 0.1);
432
+ }
433
+
434
+
435
+
436
+ .status-header {
437
+ display: flex;
438
+ justify-content: space-between;
439
+ align-items: center;
440
+ margin-bottom: 0.75rem;
441
+ }
442
+
443
+ .clear-filter {
444
+ font-size: 0.65rem;
445
+ color: var(--accent-blue);
446
+ cursor: pointer;
447
+ background: none;
448
+ border: none;
449
+ }
450
+
451
+ .status-list {
452
+ display: flex;
453
+ flex-direction: column;
454
+ gap: 0.4rem;
455
+ }
456
+
457
+ .status-item {
458
+ display: flex;
459
+ align-items: center;
460
+ gap: 0.6rem;
461
+ font-size: 0.7rem;
462
+ padding: 4px 8px;
463
+ border-radius: 6px;
464
+ cursor: pointer;
465
+ }
466
+
467
+ .status-item.active {
468
+ background: rgba(82, 175, 255, 0.1);
469
+ }
470
+
471
+ .status-dot {
472
+ width: 6px;
473
+ height: 6px;
474
+ border-radius: 50%;
475
+ }
476
+
477
+ .status-name {
478
+ flex: 1;
479
+ color: var(--text-secondary);
480
+ }
481
+
482
+ .status-label {
483
+ font-weight: 700;
484
+ font-size: 0.65rem;
485
+ }
486
+
487
+ .loader {
488
+ padding: 2rem;
489
+ text-align: center;
490
+ color: var(--text-secondary);
491
+ }
492
+
493
+
494
+
495
+ /* Support Link & Modal */
496
+ .support-link {
497
+ cursor: pointer;
498
+ color: var(--accent-gold);
499
+ font-size: 1.1rem;
500
+ transition: text-shadow 0.3s ease;
501
+ }
502
+
503
+ .support-link:hover {
504
+ text-shadow: 0 0 12px rgba(255, 215, 64, 0.5);
505
+ }
506
+
507
+ .support-modal {
508
+ display: none;
509
+ position: fixed;
510
+ top: 0;
511
+ left: 0;
512
+ width: 100%;
513
+ height: 100%;
514
+ background: rgba(0, 0, 0, 0.7);
515
+ backdrop-filter: blur(6px);
516
+ z-index: 1000;
517
+ justify-content: center;
518
+ align-items: center;
519
+ }
520
+
521
+ .support-modal.active {
522
+ display: flex;
523
+ }
524
+
525
+ .support-modal-content {
526
+ background: var(--card-bg);
527
+ border: 1px solid var(--card-border);
528
+ border-radius: 16px;
529
+ padding: 1.5rem;
530
+ text-align: center;
531
+ max-width: 320px;
532
+ width: 90%;
533
+ animation: fadeIn 0.3s ease;
534
+ }
535
+
536
+ .support-modal-content h3 {
537
+ font-size: 1.1rem;
538
+ margin-bottom: 1rem;
539
+ color: var(--accent-gold);
540
+ }
541
+
542
+ .qr-image {
543
+ width: 100%;
544
+ max-width: 240px;
545
+ border-radius: 12px;
546
+ margin-bottom: 1rem;
547
+ }
548
+
549
+ .paypal-link {
550
+ display: inline-block;
551
+ color: var(--accent-blue);
552
+ font-size: 0.85rem;
553
+ font-weight: 600;
554
+ text-decoration: none;
555
+ padding: 8px 16px;
556
+ border: 1px solid var(--accent-blue);
557
+ border-radius: 8px;
558
+ transition: background 0.2s ease;
559
+ }
560
+
561
+ .paypal-link:hover {
562
+ background: rgba(82, 175, 255, 0.15);
563
+ }
564
+
565
+ @media (max-width: 480px) {
566
+ .support-modal-content {
567
+ padding: 1rem;
568
+ max-width: 260px;
569
+ }
570
+
571
+ .qr-image {
572
+ max-width: 180px;
573
+ }
574
+ }
index.js CHANGED
@@ -1,372 +1,37 @@
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
- // RAM Caching for near-instant API responses
117
- let cachedApiResponse = '';
118
- let cachedIndicesResponse = '';
119
-
120
- function updateResponseCaches() {
121
- cachedApiResponse = JSON.stringify({
122
- news: cachedNews,
123
- status: feedStatus,
124
- trend: sentimentTrend,
125
- indices: cachedIndices
126
- });
127
- cachedIndicesResponse = JSON.stringify({ indices: cachedIndices });
128
- }
129
-
130
- function getSourceName(url, feedTitle) {
131
- if (url.includes('NSE+BSE+finance')) return 'Google Finance (Markets)';
132
- if (url.includes('site:moneycontrol.com')) return 'Moneycontrol';
133
- if (url.includes('site:financialexpress.com')) return 'Financial Express';
134
- if (url.includes('site:business-standard.com')) return 'Business Standard';
135
- if (url.includes('economictimes')) return 'Economic Times';
136
- if (url.includes('livemint')) return 'LiveMint';
137
- if (url.includes('headlines/section/topic/BUSINESS')) return 'Business News';
138
- if (url.includes('site:thehindubusinessline.com')) return 'The Hindu Business Line';
139
- if (url.includes('site:pulse.zerodha.com')) return 'Zerodha Pulse';
140
- if (url.includes('site:in.investing.com')) return 'Investing.com';
141
- if (url.includes('site:web.stockedge.com')) return 'StockEdge';
142
- if (url.includes('site:groww.in')) return 'Groww';
143
- if (url.includes('site:ndtvprofit.com')) return 'NDTV Profit';
144
- if (url.includes('site:cnbctv18.com')) return 'CNBC TV18';
145
- if (url.includes('site:zeebiz.com')) return 'Zee Business';
146
- if (url.includes('site:goodreturns.in')) return 'GoodReturns';
147
- if (url.includes('site:ticker.finology.in')) return 'Finology Ticker';
148
- if (url.includes('site:moneyworks4me.com')) return 'MoneyWorks4Me';
149
- if (url.includes('site:trendlyne.com')) return 'Trendlyne';
150
- if (url.includes('site:tickertape.in')) return 'Tickertape';
151
- if (url.includes('site:equitymaster.com')) return 'Equitymaster';
152
- if (url.includes('site:marketsmojo.com')) return 'MarketsMojo';
153
- if (url.includes('site:fortuneindia.com')) return 'Fortune India';
154
- if (url.includes('site:businessworld.in')) return 'BusinessWorld';
155
- if (url.includes('site:outlookbusiness.com')) return 'Outlook Business';
156
- if (url.includes('site:money.rediff.com')) return 'Rediff Money';
157
- if (url.includes('site:informistmedia.com')) return 'Informist Media';
158
- if (url.includes('site:reuters.com')) return 'Reuters India';
159
- if (url.includes('site:thehindu.com')) return 'The Hindu';
160
- if (url.includes('site:timesofindia.indiatimes.com')) return 'Times of India';
161
- if (url.includes('site:deccanherald.com')) return 'Deccan Herald';
162
- if (url.includes('site:bqprime.com')) return 'BQ Prime';
163
- if (url.includes('site:forbesindia.com')) return 'Forbes India';
164
- if (url.includes('site:bloomberg.com')) return 'Bloomberg India';
165
- if (url.includes('site:dsij.in')) return 'DSIJ';
166
- if (url.includes('site:economictimes.indiatimes.com/et-now')) return 'ET Now';
167
- if (url.includes('site:firstpost.com')) return 'Firstpost Business';
168
- if (url.includes('site:capitalmarket.com')) return 'Capital Market';
169
- if (url.includes('site:news18.com')) return 'News18 Business';
170
- if (url.includes('site:news.abplive.com')) return 'ABP Business';
171
- if (url.includes('site:mydigitalfc.com')) return 'Financial Chronicle';
172
- if (url.includes('site:asia.nikkei.com')) return 'Nikkei Asia (India)';
173
- if (url.includes('site:icicidirect.com')) return 'ICICIDirect Research';
174
- if (url.includes('site:hdfcsec.com')) return 'HDFC Securities';
175
- if (url.includes('site:smallcase.com')) return 'Smallcase Insights';
176
- if (url.includes('site:upstox.com')) return 'Upstox Updates';
177
- if (url.includes('site:angelone.in')) return 'Angel One';
178
- if (url.includes('businesstoday')) return 'Business Today';
179
- if (url.includes('site:screener.in')) return 'Screener.in';
180
- if (url.includes('site:capitalmind.in')) return 'CapitalMind';
181
- if (url.includes('site:thecore.in')) return 'The Core';
182
- if (url.includes('site:bseindia.com')) return 'BSE Announcements';
183
- if (url.includes('site:nseindia.com')) return 'NSE Announcements';
184
- if (url.includes('site:alphastreet.com')) return 'AlphaStreet India';
185
- if (url.includes('intitle:(dividend')) return 'Corporate Action (Dividend/Buyback)';
186
- if (url.includes('intitle:("quarterly+results"')) return 'Earnings Alert';
187
- if (url.includes('intitle:("FII+buying"')) return 'Institutional Flow Alert';
188
- if (url.includes('intitle:("SEBI"')) return 'Regulatory/Policy Alert';
189
- if (url.includes('intitle:("52-week+high"')) return 'Momentum/Breakout Alert';
190
- if (url.includes('intitle:("IPO+allotment"')) return 'IPO/Listing Alert';
191
- if (url.includes('intitle:("Auto+sales"')) return 'Sector Update';
192
- if (url.includes('intitle:("Management+change"')) return 'Management Update';
193
- if (url.includes('site:motilaloswal.com')) return 'Motilal Oswal';
194
- if (url.includes('site:finshots.in')) return 'Finshots';
195
- if (url.includes('site:tradebrains.in')) return 'Trade Brains';
196
- if (url.includes('site:valueresearchonline.com')) return 'Value Research';
197
- if (url.includes('site:entrackr.com')) return 'Entrackr (Tech Stocks)';
198
- if (url.includes('site:inc42.com')) return 'Inc42 (Tech Stocks)';
199
- if (url.includes('site:ft.com')) return 'Financial Times (India)';
200
- if (url.includes('site:economist.com')) return 'The Economist (India)';
201
- if (url.includes('site:wsj.com')) return 'WSJ (India)';
202
- if (url.includes('site:thestatesman.com')) return 'The Statesman';
203
- if (url.includes('site:dailypioneer.com')) return 'The Pioneer';
204
- if (url.includes('site:freepressjournal.in')) return 'Free Press Journal';
205
- return feedTitle?.replace('Google News - ', '') || 'Market News';
206
- }
207
-
208
- async function fetchIndices() {
209
- const tickers = {
210
- 'NIFTY 50': '^NSEI',
211
- 'BANK NIFTY': '^NSEBANK',
212
- 'SENSEX': '^BSESN',
213
- 'FINNIFTY': 'NIFTY_FIN_SERVICE.NS',
214
- 'INDIA VIX': '^INDIAVIX',
215
- 'USD/INR': 'INR=X'
216
- };
217
-
218
- const tickerEntries = Object.entries(tickers);
219
-
220
- // Fetch in parallel but keep the order
221
- const responses = await Promise.allSettled(tickerEntries.map(async ([name, ticker]) => {
222
- const response = await axios.get(`https://query1.finance.yahoo.com/v8/finance/chart/${ticker}?interval=1m&range=1d`, {
223
- headers: { 'User-Agent': BROWSER_HEADERS['User-Agent'] },
224
- timeout: 5000
225
- });
226
- const meta = response.data.chart.result[0].meta;
227
- return {
228
- price: meta.regularMarketPrice.toFixed(2),
229
- change: (meta.regularMarketPrice - meta.previousClose).toFixed(2),
230
- changePercent: (((meta.regularMarketPrice - meta.previousClose) / meta.previousClose) * 100).toFixed(2)
231
- };
232
- }));
233
-
234
- const results = {};
235
- tickerEntries.forEach(([name], index) => {
236
- const res = responses[index];
237
- if (res.status === 'fulfilled') {
238
- results[name] = res.value;
239
- } else {
240
- results[name] = cachedIndices[name] || { price: '---', change: '0', changePercent: '0' };
241
- }
242
- });
243
-
244
- cachedIndices = results;
245
- updateResponseCaches();
246
- }
247
-
248
- async function fetchAndAnalyzeNews() {
249
- if (isFetching) return;
250
- isFetching = true;
251
- console.time('News Sync');
252
- const allEntriesMap = new Map();
253
- const now = new Date();
254
- const twentyFourHoursAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
255
-
256
- const CONCURRENCY_LIMIT = 10;
257
- const results = [];
258
- const executing = [];
259
-
260
- for (const url of RSS_FEEDS) {
261
- const p = (async (targetUrl) => {
262
- try {
263
- const response = await axios.get(targetUrl, { headers: BROWSER_HEADERS, timeout: 10000 });
264
- const feed = await parser.parseString(response.data);
265
- const sourceName = getSourceName(targetUrl, feed.title);
266
- feedStatus[targetUrl] = { status: 'Success', displayName: sourceName, lastUpdate: new Date().toISOString() };
267
- return { items: feed.items, sourceName };
268
- } catch (e) {
269
- feedStatus[targetUrl] = { status: 'Failed', displayName: targetUrl.split('/')[2], lastUpdate: new Date().toISOString() };
270
- return { items: [], sourceName: 'Unknown' };
271
- }
272
- })(url).then(res => {
273
- executing.splice(executing.indexOf(p), 1);
274
- return res;
275
- });
276
- results.push(p);
277
- executing.push(p);
278
- if (executing.length >= CONCURRENCY_LIMIT) {
279
- await Promise.race(executing);
280
- }
281
- }
282
-
283
- const settledResults = await Promise.allSettled(results);
284
- let originalCount = 0;
285
- let filteredCount = 0;
286
-
287
- settledResults.forEach(result => {
288
- if (result.status === 'fulfilled') {
289
- const { items, sourceName } = result.value;
290
- items.forEach(item => {
291
- const pubDate = new Date(item.pubDate);
292
- if (pubDate >= twentyFourHoursAgo && !allEntriesMap.has(item.link)) {
293
- originalCount++;
294
- const words = item.title.trim().split(/\s+/).filter(w => /[a-zA-Z0-9]/.test(w));
295
- if (words.length >= 5) {
296
- // Sentiment analysis only for unique, filtered items
297
- const fullText = `${item.title} ${item.contentSnippet || ''}`;
298
- const analysis = sentiment.analyze(fullText);
299
-
300
- allEntriesMap.set(item.link, {
301
- id: item.guid || item.link,
302
- title: item.title,
303
- link: item.link,
304
- pubDate: item.pubDate,
305
- source: sourceName,
306
- sentiment: {
307
- score: analysis.score,
308
- label: analysis.score > 0 ? 'Positive' : (analysis.score < 0 ? 'Negative' : 'Neutral')
309
- }
310
- });
311
- filteredCount++;
312
- }
313
- }
314
- });
315
- }
316
- });
317
-
318
- console.log(`News sync: ${originalCount} unique items found, ${filteredCount} passed word filter.`);
319
- console.timeEnd('News Sync');
320
-
321
- const uniqueNews = Array.from(allEntriesMap.values());
322
- uniqueNews.sort((a, b) => new Date(b.pubDate) - new Date(a.pubDate));
323
-
324
- // Efficiently detect changes without JSON.stringify overhead
325
- const newsChanged = uniqueNews.length !== cachedNews.length ||
326
- (uniqueNews.length > 0 && cachedNews.length > 0 && uniqueNews[0].link !== cachedNews[0].link);
327
-
328
- if (newsChanged || cachedNews.length === 0) {
329
- cachedNews = uniqueNews;
330
-
331
- if (cachedNews.length > 0) {
332
- const avgSentiment = cachedNews.reduce((acc, curr) => acc + curr.sentiment.score, 0) / cachedNews.length;
333
- sentimentTrend.push({ time: new Date().toISOString(), score: avgSentiment });
334
- if (sentimentTrend.length > 50) sentimentTrend.shift();
335
- }
336
-
337
- updateResponseCaches();
338
-
339
- if (!fs.existsSync(path.join(__dirname, 'data'))) fs.mkdirSync(path.join(__dirname, 'data'));
340
- try {
341
- // Background write to avoid blocking (no await on disk I/O if possible or use a separate loop)
342
- Promise.all([
343
- fs.promises.writeFile(DATA_FILE, JSON.stringify(cachedNews)),
344
- fs.promises.writeFile(TREND_FILE, JSON.stringify(sentimentTrend))
345
- ]).catch(err => console.error('Error saving data files:', err));
346
- } catch (err) {
347
- console.error('Data persistence failed:', err);
348
- }
349
- }
350
- isFetching = false;
351
- }
352
-
353
- app.get('/api/news', (req, res) => {
354
- res.set('Content-Type', 'application/json');
355
- res.send(cachedApiResponse || JSON.stringify({ news: cachedNews, status: feedStatus, trend: sentimentTrend, indices: cachedIndices }));
356
- });
357
-
358
- app.get('/api/indices', (req, res) => {
359
- res.set('Content-Type', 'application/json');
360
- res.send(cachedIndicesResponse || JSON.stringify({ indices: cachedIndices }));
361
- });
362
-
363
- cron.schedule('*/5 * * * *', () => fetchAndAnalyzeNews());
364
- setInterval(fetchIndices, 10000);
365
-
366
- if (fs.existsSync(DATA_FILE)) { try { cachedNews = JSON.parse(fs.readFileSync(DATA_FILE)); } catch (e) { } }
367
- if (fs.existsSync(TREND_FILE)) { try { sentimentTrend = JSON.parse(fs.readFileSync(TREND_FILE)); } catch (e) { } }
368
-
369
- fetchIndices();
370
- fetchAndAnalyzeNews();
371
-
372
- app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));
 
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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
sentiment_trends.json ADDED
The diff for this file is too large to render. See raw diff
 
style.css ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ let allNews = [];
2
+ let sentimentTrend = [];
3
+ let currentFilter = 'all';
4
+ let currentSource = 'all';
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'],
11
+ pos: ['profit', 'growth', 'record', 'dividend', 'buyback', 'partnership', 'acquired', 'expansion', 'jump', 'bullish', 'buy'],
12
+ neg: ['loss', 'fall', 'slump', 'down', 'decline', 'investigation', 'scam', 'penalty', 'lawsuit', 'bearish', 'sell']
13
+ };
14
+
15
+ const IMPACT_REGEX = Object.fromEntries(
16
+ Object.entries(IMPACT_KEYWORDS).map(([type, words]) => [
17
+ type,
18
+ new RegExp(`\\b(${words.join('|')})\\b`, 'gi')
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) {
44
+ console.error('Error updating indices:', error);
45
+ }
46
+ }
47
+
48
+ function renderDashboard() {
49
+ renderFeed();
50
+ }
51
+
52
+ function renderFeed() {
53
+ const feed = document.getElementById('news-feed');
54
+ if (!feed) return;
55
+
56
+ let filtered = allNews;
57
+ if (currentFilter !== 'all') filtered = filtered.filter(n => n.sentiment.label === currentFilter);
58
+ if (currentSource !== 'all') filtered = filtered.filter(n => n.source === currentSource);
59
+ if (searchQuery) {
60
+ const query = searchQuery.toLowerCase();
61
+ filtered = filtered.filter(n =>
62
+ n.title.toLowerCase().includes(query) ||
63
+ n.source.toLowerCase().includes(query)
64
+ );
65
+ }
66
+
67
+ // Filter out headlines with less than 5 words
68
+ filtered = filtered.filter(n => {
69
+ const words = n.title.trim().split(/\s+/).filter(w => /[a-zA-Z0-9]/.test(w));
70
+ return words.length >= 5;
71
+ });
72
+
73
+ currentRenderedFiltered = filtered;
74
+ renderedCount = 0;
75
+
76
+ if (filtered.length === 0) {
77
+ feed.innerHTML = '<div class="loader">No matches for current trading filters.</div>';
78
+ return;
79
+ }
80
+
81
+ feed.innerHTML = '';
82
+ renderMoreItems();
83
+ }
84
+
85
+ function renderMoreItems() {
86
+ const feed = document.getElementById('news-feed');
87
+ if (!feed) return;
88
+
89
+ const oldTrigger = document.getElementById('load-more-trigger');
90
+ if (oldTrigger) oldTrigger.remove();
91
+
92
+ const nextCount = Math.min(renderedCount + 100, currentRenderedFiltered.length);
93
+ const itemsToRender = currentRenderedFiltered.slice(renderedCount, nextCount);
94
+
95
+ const html = itemsToRender.map((item, index) => {
96
+ const timeAgo = getTimeAgo(new Date(item.pubDate));
97
+ const highlightedTitle = highlightImpact(item.title);
98
+ return `
99
+ <article class="news-article" style="animation-delay: ${index * 0.02}s">
100
+ <div class="article-header">
101
+ <span class="source">${item.source}</span>
102
+ <span class="time">${timeAgo}</span>
103
+ </div>
104
+ <a href="${item.link}" target="_blank" style="text-decoration: none; color: inherit;">
105
+ <h3>${highlightedTitle}</h3>
106
+ </a>
107
+ </article>
108
+ `;
109
+ }).join('');
110
+
111
+ feed.insertAdjacentHTML('beforeend', html);
112
+ renderedCount = nextCount;
113
+
114
+ if (renderedCount < currentRenderedFiltered.length) {
115
+ const trigger = document.createElement('div');
116
+ trigger.id = 'load-more-trigger';
117
+ trigger.className = 'loader';
118
+ trigger.textContent = 'Scroll for more...';
119
+ feed.appendChild(trigger);
120
+ setupObserver(trigger);
121
+ }
122
+ }
123
+
124
+ function setupObserver(trigger) {
125
+ const observer = new IntersectionObserver((entries) => {
126
+ if (entries[0].isIntersecting) {
127
+ observer.disconnect();
128
+ renderMoreItems();
129
+ }
130
+ });
131
+ observer.observe(trigger);
132
+ }
133
+
134
+ function renderIndices(indices) {
135
+ const container = document.getElementById('index-ticker');
136
+ if (!container || !indices) return;
137
+
138
+ container.innerHTML = Object.entries(indices).map(([name, data]) => {
139
+ const isUp = parseFloat(data.change) >= 0;
140
+ const sign = isUp ? '+' : '';
141
+ return `
142
+ <div class="ticker-item">
143
+ <span class="ticker-name">${name}</span>
144
+ <div class="ticker-data">
145
+ <span class="ticker-price">${parseFloat(data.price).toLocaleString('en-IN', { minimumFractionDigits: 2 })}</span>
146
+ <span class="ticker-change ${isUp ? 'up' : 'down'}">${sign}${data.changePercent}%</span>
147
+ </div>
148
+ </div>
149
+ `;
150
+ }).join('');
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
+
161
+ function getTimeAgo(date) {
162
+ const seconds = Math.floor((new Date() - date) / 1000);
163
+ let interval = seconds / 3600;
164
+ if (interval > 1) return Math.floor(interval) + 'h ago';
165
+ interval = seconds / 60;
166
+ if (interval > 1) return Math.floor(interval) + 'm ago';
167
+ return 'Just now';
168
+ }
169
+
170
+ function filterBySentiment(sentiment) {
171
+ currentFilter = sentiment;
172
+ document.querySelectorAll('.filter-btn').forEach(btn => {
173
+ btn.classList.toggle('active', btn.dataset.sentiment === sentiment);
174
+ });
175
+ renderFeed();
176
+ }
177
+
178
+ let searchTimeout;
179
+ function handleSearch(val) {
180
+ clearTimeout(searchTimeout);
181
+ searchTimeout = setTimeout(() => {
182
+ searchQuery = val;
183
+ renderFeed();
184
+ }, 300);
185
+ }
186
+
187
+
188
+
189
+
190
+
191
+
192
+
193
+ function openSupportModal() {
194
+ document.getElementById('support-modal').classList.add('active');
195
+ history.pushState({ modal: 'support' }, '');
196
+ }
197
+
198
+ function closeSupportModal() {
199
+ document.getElementById('support-modal').classList.remove('active');
200
+ }
201
+
202
+ window.addEventListener('popstate', function (e) {
203
+ if (document.getElementById('support-modal').classList.contains('active')) {
204
+ closeSupportModal();
205
+ }
206
+ });
207
+
208
+
209
+ fetchNews();
210
+ setInterval(fetchNews, 30000);
211
+ setInterval(fetchIndicesOnly, 10000);
upi-qr.jpeg ADDED