sondakika 2.0.6 → 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +342 -324
  2. package/cli.js +555 -488
  3. package/package.json +25 -25
package/cli.js CHANGED
@@ -1,488 +1,555 @@
1
- #!/usr/bin/env node
2
-
3
- const blessed = require('blessed');
4
- const RssParser = require('rss-parser');
5
- const fs = require('fs');
6
- const path = require('path');
7
- const os = require('os');
8
-
9
- const packagePath = path.join(__dirname, 'package.json');
10
- const { version } = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
11
-
12
- const STATE_DIR = path.join(os.homedir(), '.config', 'sondakika-cli');
13
- const STATE_FILE = path.join(STATE_DIR, 'state.json');
14
-
15
- const defaultCLIState = {
16
- lastSource: 'trt',
17
- itemsPerPage: 10
18
- };
19
-
20
- function loadCLIState() {
21
- try {
22
- if (fs.existsSync(STATE_FILE)) {
23
- const data = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
24
- return { ...defaultCLIState, ...data };
25
- }
26
- } catch (e) {}
27
- return { ...defaultCLIState };
28
- }
29
-
30
- function saveCLIState(state) {
31
- try {
32
- if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true });
33
- fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), 'utf8');
34
- } catch (e) {}
35
- }
36
-
37
- // Sources with working URLs (mix of HTTP and HTTPS as per original root index.js)
38
- const sources = {
39
- cumhuriyet: {
40
- name: 'Cumhuriyet',
41
- url: 'https://www.cumhuriyet.com.tr/rss/son_dakika.xml',
42
- isSondakika: true
43
- },
44
- trt: {
45
- name: 'TRT Haber',
46
- url: 'https://www.trthaber.com/sondakika.rss',
47
- isSondakika: true
48
- },
49
- mynet: {
50
- name: 'Mynet',
51
- url: 'https://www.mynet.com/haber/rss/sondakika',
52
- isSondakika: true
53
- },
54
- sabah: {
55
- name: 'Sabah',
56
- url: 'https://www.sabah.com.tr/rss/anasayfa.xml',
57
- isSondakika: false
58
- },
59
- star: {
60
- name: 'Star',
61
- url: 'https://www.star.com.tr/rss/rss.asp?cid=124',
62
- isSondakika: false
63
- },
64
- vatan: {
65
- name: 'Gazete Vatan',
66
- url: 'https://www.gazetevatan.com/rss/gundem.xml',
67
- isSondakika: false
68
- },
69
- haberturk: {
70
- name: 'Haberturk',
71
- url: 'https://www.haberturk.com/rss',
72
- isSondakika: false
73
- },
74
- cnnturk: {
75
- name: 'CNN Turk',
76
- url: 'https://cnnturk.com/feed/rss/turkiye',
77
- isSondakika: false
78
- },
79
- yenisafak: {
80
- name: 'Yeni Safak',
81
- url: 'https://www.yenisafak.com/rss',
82
- isSondakika: false
83
- },
84
- aa: {
85
- name: 'Anadolu Ajansi',
86
- url: 'https://www.aa.com.tr/en/rss',
87
- isSondakika: false
88
- }
89
- };
90
-
91
- const parser = new RssParser({
92
- timeout: 10000,
93
- requestOptions: {
94
- headers: {
95
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
96
- }
97
- },
98
- maxRedirects: 5
99
- });
100
-
101
- const ITEMS_PER_PAGE = 10;
102
-
103
- function formatDate(pubDate) {
104
- if (!pubDate) return '';
105
- const date = new Date(pubDate);
106
- if (isNaN(date.getTime())) return '';
107
- return date.toLocaleDateString('tr-TR', {
108
- day: '2-digit',
109
- month: '2-digit',
110
- year: 'numeric',
111
- hour: '2-digit',
112
- minute: '2-digit'
113
- });
114
- }
115
-
116
- async function fetchNews(sourceKey, count) {
117
- const source = sources[sourceKey.toLowerCase()];
118
- if (!source) {
119
- console.error(`Unknown source: ${sourceKey}`);
120
- console.log(`Available sources: ${Object.keys(sources).join(', ')}`);
121
- process.exit(1);
122
- }
123
-
124
- try {
125
- const feed = await parser.parseURL(source.url);
126
-
127
- if (!feed.items || feed.items.length === 0) {
128
- console.error(`No items found in feed from ${source.name}`);
129
- process.exit(1);
130
- }
131
-
132
- return feed.items.slice(0, count);
133
- } catch (err) {
134
- console.error('Error fetching news:', err.message);
135
- console.error(`URL: ${source.url}`);
136
- process.exit(1);
137
- }
138
- }
139
-
140
- function wrapText(text, width) {
141
- const lines = [];
142
- let start = 0;
143
- while (start < text.length) {
144
- let end = Math.min(start + width, text.length);
145
- if (end < text.length && end > start) {
146
- const lastSpace = text.lastIndexOf(' ', end);
147
- if (lastSpace > start && lastSpace > end - width / 2) {
148
- end = lastSpace;
149
- }
150
- }
151
- lines.push(text.substring(start, end).trim());
152
- start = end;
153
- while (start < text.length && text[start] === ' ') {
154
- start++;
155
- }
156
- }
157
- return lines.length ? lines : [''];
158
- }
159
-
160
- function createScreen() {
161
- return blessed.screen({
162
- smartCSR: true,
163
- title: 'Sondakika Haberler'
164
- });
165
- }
166
-
167
- function createMainList(screen, items, sourceName) {
168
- const list = blessed.list({
169
- parent: screen,
170
- top: 2,
171
- left: 0,
172
- width: '100%',
173
- height: '100%-4',
174
- tags: true,
175
- keys: false,
176
- mouse: false,
177
- border: null,
178
- style: {
179
- selected: {
180
- fg: 'white',
181
- bg: 'blue'
182
- }
183
- },
184
- scrollbar: {
185
- ch: '█',
186
- inverse: true
187
- }
188
- });
189
-
190
- list.focus();
191
-
192
- const header = blessed.box({
193
- parent: screen,
194
- top: 0,
195
- left: 0,
196
- width: '100%',
197
- height: 2,
198
- content: `{bold}📰 ${sourceName}{/bold} - {cyan-fg}▲/▼{/cyan-fg} Seç, {cyan-fg}◄/►{/cyan-fg} Sayfa, {cyan-fg}Enter{/cyan-fg} Görüntüle, {cyan-fg}Q{/cyan-fg} Çıkış`,
199
- tags: true,
200
- style: {
201
- fg: 'white',
202
- bg: 'blue'
203
- }
204
- });
205
-
206
- const footer = blessed.box({
207
- parent: screen,
208
- bottom: 0,
209
- left: 0,
210
- width: '100%',
211
- height: 2,
212
- content: '{cyan-fg}▲{/cyan-fg} Yukarı {cyan-fg}▼{/cyan-fg} Aşağı {cyan-fg}◄{/cyan-fg} Önceki Sayfa {cyan-fg}►{/cyan-fg} Sonraki Sayfa {cyan-fg}Enter{/cyan-fg} Görüntüle {cyan-fg}Q{/cyan-fg} Çıkış',
213
- tags: true,
214
- style: {
215
- fg: 'white',
216
- bg: 'black'
217
- }
218
- });
219
-
220
- return { list, header, footer };
221
- }
222
-
223
- function createFullScreenView(screen) {
224
- const overlay = blessed.box({
225
- parent: screen,
226
- top: 0,
227
- left: 0,
228
- width: '100%',
229
- height: '100%',
230
- tags: true,
231
- border: {
232
- type: 'line'
233
- },
234
- style: {
235
- border: {
236
- fg: 'cyan'
237
- }
238
- },
239
- hidden: true,
240
- scrollable: true,
241
- alwaysScroll: true,
242
- scrolloffset: 1
243
- });
244
-
245
- const closeHint = blessed.box({
246
- parent: overlay,
247
- bottom: 0,
248
- left: 0,
249
- width: '100%',
250
- height: 1,
251
- content: '{center}Enter ile listeye dön{/center}',
252
- tags: true,
253
- style: {
254
- fg: 'black',
255
- bg: 'cyan'
256
- }
257
- });
258
-
259
- return { overlay, closeHint };
260
- }
261
-
262
- function updateListDisplay(list, items, page, totalPages) {
263
- const start = page * ITEMS_PER_PAGE;
264
- const end = Math.min(start + ITEMS_PER_PAGE, items.length);
265
- const pageItems = items.slice(start, end);
266
-
267
- const lines = pageItems.map((item, idx) => {
268
- const globalIdx = start + idx + 1;
269
- const dateStr = formatDate(item.pubDate || item.isodate);
270
- const titleLines = wrapText(item.title, 60);
271
- let line = `{bold}${globalIdx}.{/bold} ${titleLines[0]}`;
272
- if (dateStr) {
273
- line += ` {gray-fg}[${dateStr}]{/gray-fg}`;
274
- }
275
- return line;
276
- });
277
-
278
- list.setItems(lines);
279
- list.scrollTo(0);
280
- }
281
-
282
- function showFullScreen(overlay, item) {
283
- const title = item.title || 'Başlık yok';
284
- const dateStr = formatDate(item.pubDate || item.isodate);
285
- const summary = item.summary || item.contentSnippet || item.content || 'İçerik yok';
286
- const link = item.link || '';
287
-
288
- const titleLines = wrapText(title, 70);
289
- const summaryLines = wrapText(summary, 70);
290
-
291
- let content = '{bold}' + titleLines.join('\n') + '{/bold}\n\n';
292
-
293
- if (dateStr) {
294
- content += `{gray-fg}📅 ${dateStr}{/gray-fg}\n\n`;
295
- }
296
-
297
- content += summaryLines.join('\n') + '\n\n';
298
-
299
- if (link) {
300
- content += `{cyan-fg}🔗 ${link}{/cyan-fg}`;
301
- }
302
-
303
- overlay.setContent(content);
304
- overlay.show();
305
- }
306
-
307
- function hideFullScreen(overlay) {
308
- overlay.hide();
309
- }
310
-
311
- async function main() {
312
- const args = process.argv.slice(2);
313
- const cliState = loadCLIState();
314
-
315
- if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
316
- const breakingSrc = Object.entries(sources)
317
- .filter(([k, v]) => v.isSondakika)
318
- .map(([k, v]) => ` ${k.padEnd(12)} ${v.name}`)
319
- .join('\n');
320
- const generalSrc = Object.entries(sources)
321
- .filter(([k, v]) => !v.isSondakika)
322
- .map(([k, v]) => ` ${k.padEnd(12)} ${v.name}`)
323
- .join('\n');
324
- console.log(`
325
- 📰 Sondakika v${version} - Türkçe Haber Okuyucu (CLI)
326
- Tam özellikli terminal haber okuyucu
327
-
328
- Kullanım:
329
- sondakika <kaynak> [adet]
330
- sondakika --version
331
- sondakika --help
332
-
333
- Kaynaklar (Son Dakika):
334
- ${breakingSrc}
335
-
336
- Kaynaklar (Haberler):
337
- ${generalSrc}
338
-
339
- Örnekler:
340
- sondakika cumhuriyet # Cumhuriyet haberleri
341
- sondakika trt 15 # TRT 15 haber
342
- sondakika mynet 5 # Mynet 5 haber
343
- sondakika sabah # Sabah haberleri
344
- sondakika haberturk 5 # Habertürk 5 haber
345
- sondakika cnnturk # CNN Türk haberleri
346
- `);
347
- process.exit(0);
348
- }
349
-
350
- if (args[0] === '--version' || args[0] === '-v') {
351
- console.log(`sondakika v${version}`);
352
- process.exit(0);
353
- }
354
-
355
- const sourceKey = args[0] ? args[0].toLowerCase() : cliState.lastSource;
356
- const count = args[1] ? parseInt(args[1]) : 1000; // Default to max (fetch all available news)
357
-
358
- const source = sources[sourceKey];
359
- if (!source) {
360
- console.error(`Unknown source: ${sourceKey}`);
361
- console.log(`Available sources: ${Object.keys(sources).join(', ')}`);
362
- process.exit(1);
363
- }
364
-
365
- // Save the source and count for next time
366
- cliState.lastSource = sourceKey;
367
- cliState.itemsPerPage = count;
368
- saveCLIState(cliState);
369
-
370
- const sourceName = source.isSondakika ? `${source.name} (Son Dakika)` : source.name;
371
-
372
- const screen = createScreen();
373
- const { list, header, footer } = createMainList(screen, [], sourceName);
374
- const { overlay, closeHint } = createFullScreenView(screen);
375
-
376
- let items = [];
377
- let currentPage = 0;
378
- let totalPages = 0;
379
- let inFullScreen = false;
380
-
381
- const loadNews = async () => {
382
- header.setContent(`{bold}📰 ${sourceName}{/bold} - Yükleniyor...`);
383
- screen.render();
384
-
385
- items = await fetchNews(sourceKey, count);
386
- totalPages = Math.ceil(items.length / ITEMS_PER_PAGE);
387
- currentPage = 0;
388
-
389
- updateListDisplay(list, items, currentPage, totalPages);
390
- list.select(0);
391
- header.setContent(`{bold}📰 ${sourceName}{/bold} - {cyan-fg}▲/▼{/cyan-fg} Seç, {cyan-fg}◄/►{/cyan-fg} Sayfa, {cyan-fg}Enter{/cyan-fg} Görüntüle, {cyan-fg}Q{/cyan-fg} Çıkış`);
392
- footer.setContent(`{cyan-fg}▲{/cyan-fg} Yukarı {cyan-fg}▼{/cyan-fg} Aşağı {cyan-fg}◄{/cyan-fg} Önceki Sayfa (${currentPage + 1}/${totalPages}) {cyan-fg}►{/cyan-fg} Sonraki Sayfa {cyan-fg}Enter{/cyan-fg} Görüntüle {cyan-fg}Q{/cyan-fg} Çıkış`);
393
- screen.render();
394
- };
395
-
396
- const updateFooter = () => {
397
- footer.setContent(`{cyan-fg}▲{/cyan-fg} Yukarı {cyan-fg}▼{/cyan-fg} Aşağı {cyan-fg}◄{/cyan-fg} Önceki Sayfa (${currentPage + 1}/${totalPages}) {cyan-fg}►{/cyan-fg} Sonraki Sayfa {cyan-fg}Enter{/cyan-fg} Görüntüle {cyan-fg}Q{/cyan-fg} Çıkış`);
398
- };
399
-
400
- const goToPrevPage = () => {
401
- if (currentPage > 0) {
402
- currentPage--;
403
- updateListDisplay(list, items, currentPage, totalPages);
404
- list.select(0);
405
- updateFooter();
406
- screen.render();
407
- }
408
- };
409
-
410
- const goToNextPage = () => {
411
- if (currentPage < totalPages - 1) {
412
- currentPage++;
413
- updateListDisplay(list, items, currentPage, totalPages);
414
- list.select(0);
415
- updateFooter();
416
- screen.render();
417
- }
418
- };
419
-
420
- const openFullScreen = () => {
421
- const start = currentPage * ITEMS_PER_PAGE;
422
- const itemIndex = start + list.selected;
423
- if (itemIndex < items.length) {
424
- showFullScreen(overlay, items[itemIndex]);
425
- inFullScreen = true;
426
- screen.render();
427
- }
428
- };
429
-
430
- const closeFullScreen = () => {
431
- hideFullScreen(overlay);
432
- inFullScreen = false;
433
- screen.render();
434
- };
435
-
436
- screen.key(['up'], () => {
437
- if (!inFullScreen) {
438
- list.up();
439
- screen.render();
440
- }
441
- });
442
-
443
- screen.key(['down'], () => {
444
- if (!inFullScreen) {
445
- list.down();
446
- screen.render();
447
- }
448
- });
449
-
450
- screen.key(['left'], () => {
451
- if (!inFullScreen) {
452
- goToPrevPage();
453
- }
454
- });
455
-
456
- screen.key(['right'], () => {
457
- if (!inFullScreen) {
458
- goToNextPage();
459
- }
460
- });
461
-
462
- screen.key(['enter'], () => {
463
- if (inFullScreen) {
464
- closeFullScreen();
465
- } else {
466
- openFullScreen();
467
- }
468
- });
469
-
470
- // Use Escape, Q, or Ctrl+C to quit
471
- screen.unkey(['q', 'C-c']);
472
- screen.key(['escape', 'q', 'Q', 'C-c'], () => {
473
- process.exit(0);
474
- });
475
-
476
- list.on('select', (el, idx) => {
477
- openFullScreen();
478
- });
479
-
480
- await loadNews();
481
-
482
- screen.render();
483
- }
484
-
485
- main().catch(err => {
486
- console.error('Error:', err.message);
487
- process.exit(1);
488
- });
1
+ #!/usr/bin/env node
2
+
3
+ const blessed = require('blessed');
4
+ const RssParser = require('rss-parser');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+ const { exec } = require('child_process');
9
+
10
+ const packagePath = path.join(__dirname, 'package.json');
11
+ const { version } = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
12
+
13
+ const STATE_DIR = path.join(os.homedir(), '.config', 'sondakika-cli');
14
+ const STATE_FILE = path.join(STATE_DIR, 'state.json');
15
+
16
+ const defaultCLIState = {
17
+ lastSource: 'trt',
18
+ itemsPerPage: 10
19
+ };
20
+
21
+ function loadCLIState() {
22
+ try {
23
+ if (fs.existsSync(STATE_FILE)) {
24
+ const data = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
25
+ return { ...defaultCLIState, ...data };
26
+ }
27
+ } catch (e) {}
28
+ return { ...defaultCLIState };
29
+ }
30
+
31
+ function saveCLIState(state) {
32
+ try {
33
+ if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true });
34
+ fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), 'utf8');
35
+ } catch (e) {}
36
+ }
37
+
38
+ // Sources with working URLs (mix of HTTP and HTTPS as per original root index.js)
39
+ const sources = {
40
+ cumhuriyet: {
41
+ name: 'Cumhuriyet',
42
+ url: 'https://www.cumhuriyet.com.tr/rss/son_dakika.xml',
43
+ isSondakika: true
44
+ },
45
+ trt: {
46
+ name: 'TRT Haber',
47
+ url: 'https://www.trthaber.com/sondakika.rss',
48
+ isSondakika: true
49
+ },
50
+ mynet: {
51
+ name: 'Mynet',
52
+ url: 'https://www.mynet.com/haber/rss/sondakika',
53
+ isSondakika: true
54
+ },
55
+ sabah: {
56
+ name: 'Sabah',
57
+ url: 'https://www.sabah.com.tr/rss/anasayfa.xml',
58
+ isSondakika: false
59
+ },
60
+ star: {
61
+ name: 'Star',
62
+ url: 'https://www.star.com.tr/rss/rss.asp?cid=124',
63
+ isSondakika: false
64
+ },
65
+ vatan: {
66
+ name: 'Gazete Vatan',
67
+ url: 'https://www.gazetevatan.com/rss/gundem.xml',
68
+ isSondakika: false
69
+ },
70
+ haberturk: {
71
+ name: 'Haberturk',
72
+ url: 'https://www.haberturk.com/rss',
73
+ isSondakika: false
74
+ },
75
+ cnnturk: {
76
+ name: 'CNN Turk',
77
+ url: 'https://cnnturk.com/feed/rss/turkiye',
78
+ isSondakika: false
79
+ },
80
+ yenisafak: {
81
+ name: 'Yeni Safak',
82
+ url: 'https://www.yenisafak.com/rss',
83
+ isSondakika: false
84
+ },
85
+ aa: {
86
+ name: 'Anadolu Ajansi',
87
+ url: 'https://www.aa.com.tr/en/rss',
88
+ isSondakika: false
89
+ }
90
+ };
91
+
92
+ const parser = new RssParser({
93
+ timeout: 10000,
94
+ requestOptions: {
95
+ headers: {
96
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
97
+ }
98
+ },
99
+ maxRedirects: 5
100
+ });
101
+
102
+ const ITEMS_PER_PAGE = 10;
103
+
104
+ function formatDate(pubDate) {
105
+ if (!pubDate) return '';
106
+ const date = new Date(pubDate);
107
+ if (isNaN(date.getTime())) return '';
108
+ return date.toLocaleDateString('tr-TR', {
109
+ day: '2-digit',
110
+ month: '2-digit',
111
+ year: 'numeric',
112
+ hour: '2-digit',
113
+ minute: '2-digit'
114
+ });
115
+ }
116
+
117
+ async function fetchNews(sourceKey, count) {
118
+ const source = sources[sourceKey.toLowerCase()];
119
+ if (!source) {
120
+ console.error(`Unknown source: ${sourceKey}`);
121
+ console.log(`Available sources: ${Object.keys(sources).join(', ')}`);
122
+ process.exit(1);
123
+ }
124
+
125
+ try {
126
+ const feed = await parser.parseURL(source.url);
127
+
128
+ if (!feed.items || feed.items.length === 0) {
129
+ console.error(`No items found in feed from ${source.name}`);
130
+ process.exit(1);
131
+ }
132
+
133
+ return feed.items.slice(0, count);
134
+ } catch (err) {
135
+ console.error('Error fetching news:', err.message);
136
+ console.error(`URL: ${source.url}`);
137
+ process.exit(1);
138
+ }
139
+ }
140
+
141
+ function wrapText(text, width) {
142
+ const lines = [];
143
+ let start = 0;
144
+ while (start < text.length) {
145
+ let end = Math.min(start + width, text.length);
146
+ if (end < text.length && end > start) {
147
+ const lastSpace = text.lastIndexOf(' ', end);
148
+ if (lastSpace > start && lastSpace > end - width / 2) {
149
+ end = lastSpace;
150
+ }
151
+ }
152
+ lines.push(text.substring(start, end).trim());
153
+ start = end;
154
+ while (start < text.length && text[start] === ' ') {
155
+ start++;
156
+ }
157
+ }
158
+ return lines.length ? lines : [''];
159
+ }
160
+
161
+ function createScreen() {
162
+ return blessed.screen({
163
+ smartCSR: true,
164
+ title: 'Sondakika Haberler'
165
+ });
166
+ }
167
+
168
+ function createMainList(screen, items, sourceName) {
169
+ const list = blessed.list({
170
+ parent: screen,
171
+ top: 2,
172
+ left: 0,
173
+ width: '100%',
174
+ height: '100%-4',
175
+ tags: true,
176
+ keys: false,
177
+ mouse: false,
178
+ border: null,
179
+ style: {
180
+ selected: {
181
+ fg: 'white',
182
+ bg: 'blue'
183
+ }
184
+ },
185
+ scrollbar: {
186
+ ch: '█',
187
+ inverse: true
188
+ }
189
+ });
190
+
191
+ list.focus();
192
+
193
+ const header = blessed.box({
194
+ parent: screen,
195
+ top: 0,
196
+ left: 0,
197
+ width: '100%',
198
+ height: 2,
199
+ content: `{bold}📰 ${sourceName}{/bold} - {cyan-fg}▲/▼{/cyan-fg} Seç, {cyan-fg}◄/►{/cyan-fg} Sayfa, {cyan-fg}Enter{/cyan-fg} Görüntüle, {cyan-fg}Q{/cyan-fg} Çıkış`,
200
+ tags: true,
201
+ style: {
202
+ fg: 'white',
203
+ bg: 'blue'
204
+ }
205
+ });
206
+
207
+ const footer = blessed.box({
208
+ parent: screen,
209
+ bottom: 0,
210
+ left: 0,
211
+ width: '100%',
212
+ height: 2,
213
+ content: '{cyan-fg}▲{/cyan-fg} Yukarı {cyan-fg}▼{/cyan-fg} Aşağı {cyan-fg}◄{/cyan-fg} Önceki Sayfa {cyan-fg}►{/cyan-fg} Sonraki Sayfa {cyan-fg}Enter{/cyan-fg} Görüntüle {cyan-fg}Q{/cyan-fg} Çıkış',
214
+ tags: true,
215
+ style: {
216
+ fg: 'white',
217
+ bg: 'black'
218
+ }
219
+ });
220
+
221
+ return { list, header, footer };
222
+ }
223
+
224
+ function createFullScreenView(screen) {
225
+ const overlay = blessed.box({
226
+ parent: screen,
227
+ top: 0,
228
+ left: 0,
229
+ width: '100%',
230
+ height: '100%-2',
231
+ tags: true,
232
+ border: {
233
+ type: 'line'
234
+ },
235
+ style: {
236
+ border: {
237
+ fg: 'cyan'
238
+ }
239
+ },
240
+ hidden: true,
241
+ scrollable: true,
242
+ alwaysScroll: true,
243
+ scrolloffset: 1
244
+ });
245
+
246
+ const linkBar = blessed.box({
247
+ parent: screen,
248
+ bottom: 1,
249
+ left: 1,
250
+ width: '100%-2',
251
+ height: 1,
252
+ tags: true,
253
+ hidden: true,
254
+ content: '',
255
+ style: {
256
+ fg: 'cyan',
257
+ bg: 'black'
258
+ }
259
+ });
260
+
261
+ const closeHint = blessed.box({
262
+ parent: screen,
263
+ bottom: 0,
264
+ left: 0,
265
+ width: '100%',
266
+ height: 1,
267
+ content: '{center}{cyan-fg}◄{/cyan-fg} Önceki {cyan-fg}►{/cyan-fg} Sonraki Don/Ac (Enter){/center}',
268
+ tags: true,
269
+ style: {
270
+ fg: 'black',
271
+ bg: 'cyan'
272
+ }
273
+ });
274
+
275
+ return { overlay, linkBar, closeHint };
276
+ }
277
+
278
+ function updateListDisplay(list, items, page, totalPages) {
279
+ const start = page * ITEMS_PER_PAGE;
280
+ const end = Math.min(start + ITEMS_PER_PAGE, items.length);
281
+ const pageItems = items.slice(start, end);
282
+
283
+ const lines = pageItems.map((item, idx) => {
284
+ const globalIdx = start + idx + 1;
285
+ const dateStr = formatDate(item.pubDate || item.isodate);
286
+ const titleLines = wrapText(item.title, 60);
287
+ let line = `{bold}${globalIdx}.{/bold} ${titleLines[0]}`;
288
+ if (dateStr) {
289
+ line += ` {gray-fg}[${dateStr}]{/gray-fg}`;
290
+ }
291
+ return line;
292
+ });
293
+
294
+ list.setItems(lines);
295
+ list.scrollTo(0);
296
+ }
297
+
298
+ function showFullScreen(overlay, linkBar, item) {
299
+ const title = item.title || 'Başlık yok';
300
+ const dateStr = formatDate(item.pubDate || item.isodate);
301
+ const summary = item.summary || item.contentSnippet || item.content || 'İçerik yok';
302
+ const link = item.link || '';
303
+
304
+ const titleLines = wrapText(title, 70);
305
+ const summaryLines = wrapText(summary, 70);
306
+
307
+ let content = '{bold}' + titleLines.join('\n') + '{/bold}\n\n';
308
+
309
+ if (dateStr) {
310
+ content += `{gray-fg}📅 ${dateStr}{/gray-fg}\n\n`;
311
+ }
312
+
313
+ content += summaryLines.join('\n');
314
+
315
+ overlay.setContent(content);
316
+ overlay.show();
317
+
318
+ if (link) {
319
+ linkBar.setContent(`{cyan-fg}🔗 ${link}{/cyan-fg}`);
320
+ } else {
321
+ linkBar.setContent('');
322
+ }
323
+ linkBar.show();
324
+ }
325
+
326
+ function hideFullScreen(overlay, linkBar) {
327
+ overlay.hide();
328
+ linkBar.hide();
329
+ }
330
+
331
+ async function main() {
332
+ const args = process.argv.slice(2);
333
+ const cliState = loadCLIState();
334
+
335
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
336
+ const breakingSrc = Object.entries(sources)
337
+ .filter(([k, v]) => v.isSondakika)
338
+ .map(([k, v]) => ` ${k.padEnd(12)} ${v.name}`)
339
+ .join('\n');
340
+ const generalSrc = Object.entries(sources)
341
+ .filter(([k, v]) => !v.isSondakika)
342
+ .map(([k, v]) => ` ${k.padEnd(12)} ${v.name}`)
343
+ .join('\n');
344
+ console.log(`
345
+ 📰 Sondakika v${version} - Türkçe Haber Okuyucu (CLI)
346
+ Tam özellikli terminal haber okuyucu
347
+
348
+ Kullanım:
349
+ sondakika <kaynak> [adet]
350
+ sondakika --version
351
+ sondakika --help
352
+
353
+ Kaynaklar (Son Dakika):
354
+ ${breakingSrc}
355
+
356
+ Kaynaklar (Haberler):
357
+ ${generalSrc}
358
+
359
+ Örnekler:
360
+ sondakika cumhuriyet # Cumhuriyet haberleri
361
+ sondakika trt 15 # TRT 15 haber
362
+ sondakika mynet 5 # Mynet 5 haber
363
+ sondakika sabah # Sabah haberleri
364
+ sondakika haberturk 5 # Habertürk 5 haber
365
+ sondakika cnnturk # CNN Türk haberleri
366
+ `);
367
+ process.exit(0);
368
+ }
369
+
370
+ if (args[0] === '--version' || args[0] === '-v') {
371
+ console.log(`sondakika v${version}`);
372
+ process.exit(0);
373
+ }
374
+
375
+ const sourceKey = args[0] ? args[0].toLowerCase() : cliState.lastSource;
376
+ const count = args[1] ? parseInt(args[1]) : 1000; // Default to max (fetch all available news)
377
+
378
+ const source = sources[sourceKey];
379
+ if (!source) {
380
+ console.error(`Unknown source: ${sourceKey}`);
381
+ console.log(`Available sources: ${Object.keys(sources).join(', ')}`);
382
+ process.exit(1);
383
+ }
384
+
385
+ // Save the source and count for next time
386
+ cliState.lastSource = sourceKey;
387
+ cliState.itemsPerPage = count;
388
+ saveCLIState(cliState);
389
+
390
+ const sourceName = source.isSondakika ? `${source.name} (Son Dakika)` : source.name;
391
+
392
+ const screen = createScreen();
393
+ const { list, header, footer } = createMainList(screen, [], sourceName);
394
+ const { overlay, linkBar, closeHint } = createFullScreenView(screen);
395
+
396
+ let items = [];
397
+ let currentPage = 0;
398
+ let totalPages = 0;
399
+ let inFullScreen = false;
400
+ let currentItemIndex = 0;
401
+
402
+ const loadNews = async () => {
403
+ header.setContent(`{bold}📰 ${sourceName}{/bold} - Yükleniyor...`);
404
+ screen.render();
405
+
406
+ items = await fetchNews(sourceKey, count);
407
+ totalPages = Math.ceil(items.length / ITEMS_PER_PAGE);
408
+ currentPage = 0;
409
+
410
+ updateListDisplay(list, items, currentPage, totalPages);
411
+ list.select(0);
412
+ header.setContent(`{bold}📰 ${sourceName}{/bold} - {cyan-fg}▲/▼{/cyan-fg} Seç, {cyan-fg}◄/►{/cyan-fg} Sayfa, {cyan-fg}Enter{/cyan-fg} Görüntüle, {cyan-fg}Q{/cyan-fg} Çıkış`);
413
+ footer.setContent(`{cyan-fg}▲{/cyan-fg} Yukarı {cyan-fg}▼{/cyan-fg} Aşağı {cyan-fg}◄{/cyan-fg} Önceki Sayfa (${currentPage + 1}/${totalPages}) {cyan-fg}►{/cyan-fg} Sonraki Sayfa {cyan-fg}Enter{/cyan-fg} Görüntüle {cyan-fg}Q{/cyan-fg} Çıkış`);
414
+ screen.render();
415
+ };
416
+
417
+ const updateFooter = () => {
418
+ footer.setContent(`{cyan-fg}▲{/cyan-fg} Yukarı {cyan-fg}▼{/cyan-fg} Aşağı {cyan-fg}◄{/cyan-fg} Önceki Sayfa (${currentPage + 1}/${totalPages}) {cyan-fg}►{/cyan-fg} Sonraki Sayfa {cyan-fg}Enter{/cyan-fg} Görüntüle {cyan-fg}Q{/cyan-fg} Çıkış`);
419
+ };
420
+
421
+ const goToPrevPage = () => {
422
+ if (currentPage > 0) {
423
+ currentPage--;
424
+ updateListDisplay(list, items, currentPage, totalPages);
425
+ list.select(0);
426
+ updateFooter();
427
+ screen.render();
428
+ }
429
+ };
430
+
431
+ const goToNextPage = () => {
432
+ if (currentPage < totalPages - 1) {
433
+ currentPage++;
434
+ updateListDisplay(list, items, currentPage, totalPages);
435
+ list.select(0);
436
+ updateFooter();
437
+ screen.render();
438
+ }
439
+ };
440
+
441
+ const openFullScreen = () => {
442
+ const start = currentPage * ITEMS_PER_PAGE;
443
+ currentItemIndex = start + list.selected;
444
+ if (currentItemIndex < items.length) {
445
+ showFullScreen(overlay, linkBar, items[currentItemIndex]);
446
+ inFullScreen = true;
447
+ screen.render();
448
+ }
449
+ };
450
+
451
+ const closeFullScreen = () => {
452
+ hideFullScreen(overlay, linkBar);
453
+ inFullScreen = false;
454
+ screen.render();
455
+ };
456
+
457
+ const goToPrevItem = () => {
458
+ if (inFullScreen && currentItemIndex > 0) {
459
+ currentItemIndex--;
460
+ showFullScreen(overlay, linkBar, items[currentItemIndex]);
461
+ const newPage = Math.floor(currentItemIndex / ITEMS_PER_PAGE);
462
+ const newSelected = currentItemIndex % ITEMS_PER_PAGE;
463
+ if (newPage !== currentPage) {
464
+ currentPage = newPage;
465
+ updateListDisplay(list, items, currentPage, totalPages);
466
+ updateFooter();
467
+ }
468
+ list.select(newSelected);
469
+ screen.render();
470
+ }
471
+ };
472
+
473
+ const goToNextItem = () => {
474
+ if (inFullScreen && currentItemIndex < items.length - 1) {
475
+ currentItemIndex++;
476
+ showFullScreen(overlay, linkBar, items[currentItemIndex]);
477
+ const newPage = Math.floor(currentItemIndex / ITEMS_PER_PAGE);
478
+ const newSelected = currentItemIndex % ITEMS_PER_PAGE;
479
+ if (newPage !== currentPage) {
480
+ currentPage = newPage;
481
+ updateListDisplay(list, items, currentPage, totalPages);
482
+ updateFooter();
483
+ }
484
+ list.select(newSelected);
485
+ screen.render();
486
+ }
487
+ };
488
+
489
+ screen.key(['up'], () => {
490
+ if (!inFullScreen) {
491
+ list.up();
492
+ screen.render();
493
+ }
494
+ });
495
+
496
+ screen.key(['down'], () => {
497
+ if (!inFullScreen) {
498
+ list.down();
499
+ screen.render();
500
+ }
501
+ });
502
+
503
+ screen.key(['left'], () => {
504
+ if (inFullScreen) {
505
+ goToPrevItem();
506
+ } else {
507
+ goToPrevPage();
508
+ }
509
+ });
510
+
511
+ screen.key(['right'], () => {
512
+ if (inFullScreen) {
513
+ goToNextItem();
514
+ } else {
515
+ goToNextPage();
516
+ }
517
+ });
518
+
519
+ screen.key(['o', 'O'], () => {
520
+ if (inFullScreen && currentItemIndex < items.length) {
521
+ const link = items[currentItemIndex].link;
522
+ if (link) {
523
+ const cmd = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';
524
+ exec(`${cmd} "${link}"`);
525
+ }
526
+ }
527
+ });
528
+
529
+ screen.key(['enter'], () => {
530
+ if (inFullScreen) {
531
+ closeFullScreen();
532
+ } else {
533
+ openFullScreen();
534
+ }
535
+ });
536
+
537
+ // Use Escape, Q, or Ctrl+C to quit
538
+ screen.unkey(['q', 'C-c']);
539
+ screen.key(['escape', 'q', 'Q', 'C-c'], () => {
540
+ process.exit(0);
541
+ });
542
+
543
+ list.on('select', (el, idx) => {
544
+ openFullScreen();
545
+ });
546
+
547
+ await loadNews();
548
+
549
+ screen.render();
550
+ }
551
+
552
+ main().catch(err => {
553
+ console.error('Error:', err.message);
554
+ process.exit(1);
555
+ });