sondakika 2.0.1 → 2.0.3
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.
- package/README.md +13 -13
- package/cli.js +466 -80
- package/package.json +12 -47
- package/AGENTS.md +0 -184
- package/assets/icon.ico +0 -0
- package/assets/icon.png +0 -0
- package/assets/icon.svg +0 -21
- package/build-index.js +0 -1137
- package/build.js +0 -1117
- package/create-multi-icon.js +0 -47
- package/fix-all-issues.js +0 -55
- package/fix-all.js +0 -33
- package/fix-comprehensive.js +0 -52
- package/fix-final.js +0 -54
- package/fix-icon.js +0 -32
- package/fix-index.js +0 -60
- package/fix-syntax.js +0 -53
- package/fix-unicode.js +0 -66
- package/generate-index.js +0 -1136
- package/generate_main.js +0 -338
- package/index.js +0 -438
- package/src/main/article-preload.js +0 -12
- package/src/main/main.js +0 -366
- package/src/main/main.js.new +0 -20
- package/src/main/preload.js +0 -17
- package/src/renderer/article-view.html +0 -649
- package/src/renderer/index.html +0 -1258
package/README.md
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
# Sondakika
|
|
2
|
-
|
|
3
2
|
**Windows için Modern Electron Tabanlı RSS Haber Okuyucu + Eski CLI Aracı**
|
|
4
3
|
*Türkçe haberleri gerçek zamanlı toplayan yerel masaüstü uygulaması, artı orijinal terminal tabanlı CLI işlevselliği*
|
|
5
4
|
|
|
@@ -11,8 +10,8 @@
|
|
|
11
10
|
---
|
|
12
11
|
|
|
13
12
|
## 📋 İçindekiler
|
|
14
|
-
1. [Genel Bakış](
|
|
15
|
-
2. [🪟 Windows Masaüstü Uygulaması (v2.0.0 GUI)](#-windows-masaüstü-uygulaması-v200-gui
|
|
13
|
+
1. [Genel Bakış](#genel-bakış)
|
|
14
|
+
2. [🪟 Windows Masaüstü Uygulaması (v2.0.0 GUI)](#-windows-masaüstü-uygulaması-v200-gui)
|
|
16
15
|
- [Sondakika Nedir?](#sondakika-nedir)
|
|
17
16
|
- [Nasıl Çalışır?](#nasıl-çalışır)
|
|
18
17
|
- [Sistem Gereksinimleri](#sistem-gereksinimleri)
|
|
@@ -29,23 +28,23 @@
|
|
|
29
28
|
5. [🛠️ Kaynaktan Derleme](#️-kaynaktan-derleme)
|
|
30
29
|
6. [📦 v2.0.0'da Neler Yaptık?](#-v200da-neler-yaptık)
|
|
31
30
|
7. [🤝 Katkıda Bulunma](#-katkıda-bulunma)
|
|
32
|
-
8. [📄 Lisans](#-lisans
|
|
31
|
+
8. [📄 Lisans](#-lisans)
|
|
33
32
|
|
|
34
33
|
---
|
|
35
34
|
|
|
36
|
-
##
|
|
37
|
-
Sondakika iki modda
|
|
35
|
+
## Genel Bakış
|
|
36
|
+
Sondakika iki modda sunulur:
|
|
38
37
|
- **v2.0.0+**: Uygulama içi makale okuyucu, temalandırma ve klavye navigasyonu içeren tam özellikli yerel Windows Electron GUI
|
|
39
|
-
- **v1.x Eski**: Türk RSS beslemelerinden
|
|
38
|
+
- **v1.x Eski**: Türk RSS beslemelerinden terminalde doğrudan haber getirmek için hafif terminal tabanlı CLI aracı
|
|
40
39
|
|
|
41
|
-
Varsayılan `npm start` komutu v2.0.0 Electron GUI'sini çalıştırır. Eski CLI işlevselliği, terminal tabanlı iş akışlarını tercih eden kullanıcılar için korunmuştur (tam CLI kodu için v1.x sürümlerine bakın).
|
|
40
|
+
Varsayılan `npm start` komutu v2.0.0 Electron GUI'sini çalıştırır. Eski CLI işlevselliği, terminal tabanlı iş akışlarını tercih eden kullanıcılar için korunmuştur (tam CLI kodu için [v1.x sürümlerine](https://github.com/eaeoz/sondakika/releases) bakın).
|
|
42
41
|
|
|
43
42
|
---
|
|
44
43
|
|
|
45
44
|
## 🪟 Windows Masaüstü Uygulaması (v2.0.0 GUI)
|
|
46
45
|
|
|
47
46
|
### Sondakika Nedir?
|
|
48
|
-
9 büyük Türk haber kaynağından (Cumhuriyet, TRT Haber, Mynet, Sabah, Star, Gazete Vatan, Habertürk, CNN Türk, Yeni Şafak, Anadolu Ajansı) gerçek zamanlı haberleri modern ve kolay okunabilir bir arayüzde getiren yerel bir Windows
|
|
47
|
+
9 büyük Türk haber kaynağından (Cumhuriyet, TRT Haber, Mynet, Sabah, Star, Gazete Vatan, Habertürk, CNN Türk, Yeni Şafak, Anadolu Ajansı) gerçek zamanlı haberleri modern ve kolay okunabilir bir arayüzde getiren yerel bir Windows uygulamasıdır.
|
|
49
48
|
|
|
50
49
|
### Nasıl Çalışır?
|
|
51
50
|
1. Uygulama, yapılandırılmış Türk haber kaynaklarından RSS beslemelerini almak için `rss-parser` kullanır
|
|
@@ -62,7 +61,6 @@ Varsayılan `npm start` komutu v2.0.0 Electron GUI'sini çalıştırır. Eski CL
|
|
|
62
61
|
- Haberleri çekmek için internet bağlantısı
|
|
63
62
|
|
|
64
63
|
### 📥 İndirme
|
|
65
|
-
|
|
66
64
|
Windows yükleyicisini doğrudan indirin:
|
|
67
65
|
|
|
68
66
|
[-blue?style=for-the-badge)](https://github.com/eaeoz/sondakika/releases/download/2.0.0/Sondakika.Setup.2.0.0.exe)
|
|
@@ -131,6 +129,9 @@ Veya tüm sürümler için (v1.x CLI-only sürümleri dahil) [GitHub Releases sa
|
|
|
131
129
|
|
|
132
130
|
---
|
|
133
131
|
|
|
132
|
+
## ⌨️ Eski CLI Kullanımı (v1.x)
|
|
133
|
+
*Not: v2.0.0 kod tabanı Electron GUI'sine odaklanmıştır. Aşağıdaki CLI işlevselliği v1.x sürümlerinden korunmuştur. Tam CLI aracını kullanmak için [v1.x sürümlerine](https://github.com/eaeoz/sondakika/releases) bakın veya `npm install -g sondakika@1.x` ile eski sürümü yükleyin.*
|
|
134
|
+
|
|
134
135
|
### CLI Özellikleri
|
|
135
136
|
- 📰 Birden fazla Türk haber kaynağından haber getir
|
|
136
137
|
- 🔗 Terminalde tıklanabilir URL'ler (iTerm2, Windows Terminal, macOS Terminal)
|
|
@@ -140,12 +141,12 @@ Veya tüm sürümler için (v1.x CLI-only sürümleri dahil) [GitHub Releases sa
|
|
|
140
141
|
### CLI Kurulumu
|
|
141
142
|
#### npx kullanarak (kurulum gerektirmez, sadece v1.x)
|
|
142
143
|
```bash
|
|
143
|
-
npx sondakika trt
|
|
144
|
+
npx sondakika@1.x trt
|
|
144
145
|
```
|
|
145
146
|
|
|
146
147
|
#### Global kurulum (v1.x)
|
|
147
148
|
```bash
|
|
148
|
-
npm install -g sondakika
|
|
149
|
+
npm install -g sondakika@1.x
|
|
149
150
|
```
|
|
150
151
|
|
|
151
152
|
### CLI Kullanım Örnekleri
|
|
@@ -255,7 +256,6 @@ Eski CLI işlevselliği üzerinde çalışmak için [eski sürümlerdeki](https:
|
|
|
255
256
|
---
|
|
256
257
|
|
|
257
258
|
## 📦 v2.0.0'da Neler Yaptık?
|
|
258
|
-
|
|
259
259
|
Bu sürüm, orijinal CLI aracından tam özellikli bir Windows masaüstü uygulamasına tam geçiştir:
|
|
260
260
|
|
|
261
261
|
### Teknik Uygulama
|
package/cli.js
CHANGED
|
@@ -2,101 +2,487 @@
|
|
|
2
2
|
|
|
3
3
|
const blessed = require('blessed');
|
|
4
4
|
const RssParser = require('rss-parser');
|
|
5
|
-
const
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
6
8
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
yenisafak: { name: 'Yeni Şafak', url: 'https://www.yenisafak.com/rss/son-dakika.xml', type: 'news' },
|
|
17
|
-
aa: { name: 'Anadolu Ajansı', url: 'https://www.aa.com.tr/tr/rss/default?cat=guncel', type: 'news' }
|
|
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
18
|
};
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
+
}
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
+
}
|
|
27
36
|
|
|
28
|
-
|
|
29
|
-
|
|
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
|
+
};
|
|
30
90
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
vatan - Gazete Vatan
|
|
41
|
-
haberturk - Habertürk
|
|
42
|
-
cnnturk - CNN Türk
|
|
43
|
-
yenisafak - Yeni Şafak
|
|
44
|
-
aa - Anadolu Ajansı
|
|
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
|
+
});
|
|
45
100
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
+
});
|
|
52
114
|
}
|
|
53
115
|
|
|
54
|
-
|
|
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
|
+
}
|
|
55
123
|
|
|
56
|
-
async function fetchNews() {
|
|
57
124
|
try {
|
|
58
125
|
const feed = await parser.parseURL(source.url);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
items.forEach((item, index) => {
|
|
83
|
-
const date = item.pubDate ? new Date(item.pubDate).toLocaleString('tr-TR') : '';
|
|
84
|
-
console.log(` ┌─ ${index + 1}. ${item.title}`);
|
|
85
|
-
console.log(` │`);
|
|
86
|
-
console.log(` │ 📅 ${date}`);
|
|
87
|
-
console.log(` │`);
|
|
88
|
-
if (item.contentSnippet) {
|
|
89
|
-
const snippet = item.contentSnippet.substring(0, 100).replace(/\n/g, ' ');
|
|
90
|
-
console.log(` │ ${snippet}...`);
|
|
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;
|
|
91
149
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
+
}
|
|
95
306
|
|
|
96
|
-
|
|
97
|
-
|
|
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(', ')}`);
|
|
98
362
|
process.exit(1);
|
|
99
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();
|
|
100
483
|
}
|
|
101
484
|
|
|
102
|
-
|
|
485
|
+
main().catch(err => {
|
|
486
|
+
console.error('Error:', err.message);
|
|
487
|
+
process.exit(1);
|
|
488
|
+
});
|