datamk commited on
Commit
b54e097
·
verified ·
1 Parent(s): 96827ed

Upload 11 files

Browse files
data/news_sentiment.json CHANGED
The diff for this file is too large to render. See raw diff
 
data/sentiment_trends.json CHANGED
@@ -1,24 +1,4 @@
1
  [
2
- {
3
- "time": "2026-02-17T21:55:04.627Z",
4
- "score": 2.178794178794179
5
- },
6
- {
7
- "time": "2026-02-17T22:00:04.836Z",
8
- "score": 2.1621052631578945
9
- },
10
- {
11
- "time": "2026-02-17T22:05:04.721Z",
12
- "score": 2.1461377870563676
13
- },
14
- {
15
- "time": "2026-02-17T22:10:04.944Z",
16
- "score": 2.1596638655462184
17
- },
18
- {
19
- "time": "2026-02-17T22:15:06.180Z",
20
- "score": 2.214723926380368
21
- },
22
  {
23
  "time": "2026-02-17T22:20:04.427Z",
24
  "score": 2.2378947368421054
@@ -198,5 +178,25 @@
198
  {
199
  "time": "2026-03-20T19:02:02.894Z",
200
  "score": 1.3021523178807948
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  }
202
  ]
 
1
  [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  {
3
  "time": "2026-02-17T22:20:04.427Z",
4
  "score": 2.2378947368421054
 
178
  {
179
  "time": "2026-03-20T19:02:02.894Z",
180
  "score": 1.3021523178807948
181
+ },
182
+ {
183
+ "time": "2026-03-22T18:49:43.688Z",
184
+ "score": 0.5806772908366534
185
+ },
186
+ {
187
+ "time": "2026-03-22T18:50:00.414Z",
188
+ "score": 0.5806772908366534
189
+ },
190
+ {
191
+ "time": "2026-03-22T18:51:47.906Z",
192
+ "score": 0.5150300601202404
193
+ },
194
+ {
195
+ "time": "2026-03-22T18:52:00.825Z",
196
+ "score": 0.5150300601202404
197
+ },
198
+ {
199
+ "time": "2026-03-22T18:54:01.373Z",
200
+ "score": 0.4885799404170804
201
  }
202
  ]
index.js CHANGED
@@ -6,14 +6,16 @@ const cron = require('node-cron');
6
  const cors = require('cors');
7
  const fs = require('fs');
8
  const path = require('path');
 
9
 
10
  const app = express();
11
  const parser = new Parser();
12
  const sentiment = new Sentiment();
13
  const PORT = 3001;
14
 
 
15
  app.use(cors());
16
- app.use(express.static('public'));
17
 
18
  const DATA_FILE = path.join(__dirname, 'data', 'news_sentiment.json');
19
  const TREND_FILE = path.join(__dirname, 'data', 'sentiment_trends.json');
@@ -117,19 +119,20 @@ async function fetchAndAnalyzeNews() {
117
  const now = new Date();
118
  const twentyFourHoursAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
119
 
120
- for (const url of RSS_FEEDS) {
121
  try {
122
  const response = await axios.get(url, { headers: BROWSER_HEADERS, timeout: 10000 });
123
  const feed = await parser.parseString(response.data);
124
  const sourceName = getSourceName(url, feed.title);
125
  feedStatus[url] = { status: 'Success', displayName: sourceName, lastUpdate: new Date().toISOString() };
126
 
 
127
  feed.items.forEach(item => {
128
  const pubDate = new Date(item.pubDate);
129
  if (pubDate >= twentyFourHoursAgo) {
130
  const fullText = `${item.title} ${item.contentSnippet || ''}`;
131
  const analysis = sentiment.analyze(fullText);
132
- allEntries.push({
133
  id: item.guid || item.link,
134
  title: item.title,
135
  link: item.link,
@@ -142,10 +145,19 @@ async function fetchAndAnalyzeNews() {
142
  });
143
  }
144
  });
 
145
  } catch (e) {
146
  feedStatus[url] = { status: 'Failed', displayName: url.split('/')[2], lastUpdate: new Date().toISOString() };
 
147
  }
148
- }
 
 
 
 
 
 
 
149
 
150
  const uniqueNews = Array.from(new Map(allEntries.map(item => [item.link, item])).values());
151
  uniqueNews.sort((a, b) => new Date(b.pubDate) - new Date(a.pubDate));
 
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');
 
119
  const now = new Date();
120
  const twentyFourHoursAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
121
 
122
+ const fetchPromises = RSS_FEEDS.map(async (url) => {
123
  try {
124
  const response = await axios.get(url, { headers: BROWSER_HEADERS, timeout: 10000 });
125
  const feed = await parser.parseString(response.data);
126
  const sourceName = getSourceName(url, feed.title);
127
  feedStatus[url] = { status: 'Success', displayName: sourceName, lastUpdate: new Date().toISOString() };
128
 
129
+ const items = [];
130
  feed.items.forEach(item => {
131
  const pubDate = new Date(item.pubDate);
132
  if (pubDate >= twentyFourHoursAgo) {
133
  const fullText = `${item.title} ${item.contentSnippet || ''}`;
134
  const analysis = sentiment.analyze(fullText);
135
+ items.push({
136
  id: item.guid || item.link,
137
  title: item.title,
138
  link: item.link,
 
145
  });
146
  }
147
  });
148
+ return items;
149
  } catch (e) {
150
  feedStatus[url] = { status: 'Failed', displayName: url.split('/')[2], lastUpdate: new Date().toISOString() };
151
+ return [];
152
  }
153
+ });
154
+
155
+ const results = await Promise.allSettled(fetchPromises);
156
+ results.forEach(result => {
157
+ if (result.status === 'fulfilled') {
158
+ allEntries.push(...result.value);
159
+ }
160
+ });
161
 
162
  const uniqueNews = Array.from(new Map(allEntries.map(item => [item.link, item])).values());
163
  uniqueNews.sort((a, b) => new Date(b.pubDate) - new Date(a.pubDate));
package-lock.json CHANGED
@@ -10,6 +10,7 @@
10
  "license": "ISC",
11
  "dependencies": {
12
  "axios": "^1.13.5",
 
13
  "cors": "^2.8.6",
14
  "express": "^5.2.1",
15
  "node-cron": "^4.2.1",
@@ -121,6 +122,60 @@
121
  "node": ">= 0.8"
122
  }
123
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  "node_modules/content-disposition": {
125
  "version": "1.0.1",
126
  "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
@@ -716,6 +771,15 @@
716
  "node": ">= 0.8"
717
  }
718
  },
 
 
 
 
 
 
 
 
 
719
  "node_modules/once": {
720
  "version": "1.4.0",
721
  "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -828,6 +892,26 @@
828
  "xml2js": "^0.5.0"
829
  }
830
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
831
  "node_modules/safer-buffer": {
832
  "version": "2.1.2",
833
  "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
 
10
  "license": "ISC",
11
  "dependencies": {
12
  "axios": "^1.13.5",
13
+ "compression": "^1.8.1",
14
  "cors": "^2.8.6",
15
  "express": "^5.2.1",
16
  "node-cron": "^4.2.1",
 
122
  "node": ">= 0.8"
123
  }
124
  },
125
+ "node_modules/compressible": {
126
+ "version": "2.0.18",
127
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
128
+ "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
129
+ "license": "MIT",
130
+ "dependencies": {
131
+ "mime-db": ">= 1.43.0 < 2"
132
+ },
133
+ "engines": {
134
+ "node": ">= 0.6"
135
+ }
136
+ },
137
+ "node_modules/compression": {
138
+ "version": "1.8.1",
139
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
140
+ "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
141
+ "license": "MIT",
142
+ "dependencies": {
143
+ "bytes": "3.1.2",
144
+ "compressible": "~2.0.18",
145
+ "debug": "2.6.9",
146
+ "negotiator": "~0.6.4",
147
+ "on-headers": "~1.1.0",
148
+ "safe-buffer": "5.2.1",
149
+ "vary": "~1.1.2"
150
+ },
151
+ "engines": {
152
+ "node": ">= 0.8.0"
153
+ }
154
+ },
155
+ "node_modules/compression/node_modules/debug": {
156
+ "version": "2.6.9",
157
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
158
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
159
+ "license": "MIT",
160
+ "dependencies": {
161
+ "ms": "2.0.0"
162
+ }
163
+ },
164
+ "node_modules/compression/node_modules/ms": {
165
+ "version": "2.0.0",
166
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
167
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
168
+ "license": "MIT"
169
+ },
170
+ "node_modules/compression/node_modules/negotiator": {
171
+ "version": "0.6.4",
172
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
173
+ "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
174
+ "license": "MIT",
175
+ "engines": {
176
+ "node": ">= 0.6"
177
+ }
178
+ },
179
  "node_modules/content-disposition": {
180
  "version": "1.0.1",
181
  "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
 
771
  "node": ">= 0.8"
772
  }
773
  },
774
+ "node_modules/on-headers": {
775
+ "version": "1.1.0",
776
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
777
+ "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
778
+ "license": "MIT",
779
+ "engines": {
780
+ "node": ">= 0.8"
781
+ }
782
+ },
783
  "node_modules/once": {
784
  "version": "1.4.0",
785
  "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
 
892
  "xml2js": "^0.5.0"
893
  }
894
  },
895
+ "node_modules/safe-buffer": {
896
+ "version": "5.2.1",
897
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
898
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
899
+ "funding": [
900
+ {
901
+ "type": "github",
902
+ "url": "https://github.com/sponsors/feross"
903
+ },
904
+ {
905
+ "type": "patreon",
906
+ "url": "https://www.patreon.com/feross"
907
+ },
908
+ {
909
+ "type": "consulting",
910
+ "url": "https://feross.org/support"
911
+ }
912
+ ],
913
+ "license": "MIT"
914
+ },
915
  "node_modules/safer-buffer": {
916
  "version": "2.1.2",
917
  "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
package.json CHANGED
@@ -12,6 +12,7 @@
12
  "type": "commonjs",
13
  "dependencies": {
14
  "axios": "^1.13.5",
 
15
  "cors": "^2.8.6",
16
  "express": "^5.2.1",
17
  "node-cron": "^4.2.1",
 
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",
public/app.js CHANGED
@@ -60,7 +60,10 @@ function renderFeed() {
60
  return;
61
  }
62
 
63
- feed.innerHTML = filtered.map((item, index) => {
 
 
 
64
  const timeAgo = getTimeAgo(new Date(item.pubDate));
65
  const highlightedTitle = highlightImpact(item.title);
66
  return `
@@ -124,9 +127,13 @@ function filterBySentiment(sentiment) {
124
  renderFeed();
125
  }
126
 
 
127
  function handleSearch(val) {
128
- searchQuery = val;
129
- renderFeed();
 
 
 
130
  }
131
 
132
 
 
60
  return;
61
  }
62
 
63
+ const maxItems = 100;
64
+ const itemsToRender = filtered.slice(0, maxItems);
65
+
66
+ feed.innerHTML = itemsToRender.map((item, index) => {
67
  const timeAgo = getTimeAgo(new Date(item.pubDate));
68
  const highlightedTitle = highlightImpact(item.title);
69
  return `
 
127
  renderFeed();
128
  }
129
 
130
+ let searchTimeout;
131
  function handleSearch(val) {
132
+ clearTimeout(searchTimeout);
133
+ searchTimeout = setTimeout(() => {
134
+ searchQuery = val;
135
+ renderFeed();
136
+ }, 300);
137
  }
138
 
139