sondakika 2.0.0 → 2.0.2

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/index.js DELETED
@@ -1,438 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const Parser = require('rss-parser')
4
- const fs = require('fs')
5
- const path = require('path')
6
- const os = require('os')
7
- const blessed = require('blessed')
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: 'ntv',
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
- const sources = {
38
- cumhuriyet: 'http://www.cumhuriyet.com.tr/rss/son_dakika.xml',
39
- trt: 'https://www.trthaber.com/sondakika.rss',
40
- mynet: 'https://www.mynet.com/haber/rss/sondakika',
41
- sabah: 'https://www.sabah.com.tr/rss/anasayfa.xml',
42
- star: 'https://www.star.com.tr/rss/rss.asp?cid=124',
43
- vatan: 'https://www.gazetevatan.com/rss/gundem.xml',
44
- haberturk: 'https://www.haberturk.com/rss',
45
- cnnturk: 'https://cnnturk.com/feed/rss/turkiye',
46
- yenisafak: 'https://www.yenisafak.com/rss',
47
- aa: 'https://www.aa.com.tr/en/rss'
48
- }
49
-
50
- const sourceInfo = {
51
- cumhuriyet: { name: 'Cumhuriyet', isSondakika: true },
52
- trt: { name: 'TRT Haber', isSondakika: true },
53
- mynet: { name: 'Mynet', isSondakika: true },
54
- sabah: { name: 'Sabah', isSondakika: false },
55
- star: { name: 'Star', isSondakika: false },
56
- vatan: { name: 'Gazete Vatan', isSondakika: false },
57
- haberturk: { name: 'Habertürk', isSondakika: false },
58
- cnnturk: { name: 'CNN Türk', isSondakika: false },
59
- yenisafak: { name: 'Yeni Şafak', isSondakika: false },
60
- aa: { name: 'Anadolu Ajansı', isSondakika: false }
61
- }
62
-
63
- const parser = new Parser()
64
- const ITEMS_PER_PAGE = 10
65
-
66
- function formatDate(pubDate) {
67
- if (!pubDate) return ''
68
- const date = new Date(pubDate)
69
- if (isNaN(date.getTime())) return ''
70
- return date.toLocaleDateString('tr-TR', {
71
- day: '2-digit',
72
- month: '2-digit',
73
- year: 'numeric',
74
- hour: '2-digit',
75
- minute: '2-digit'
76
- })
77
- }
78
-
79
- async function fetchNews(source, count) {
80
- const url = sources[source.toLowerCase()]
81
- if (!url) {
82
- console.error(`Unknown source: ${source}`)
83
- console.log(`Available sources: ${Object.keys(sources).join(', ')}`)
84
- process.exit(1)
85
- }
86
-
87
- try {
88
- const feed = await parser.parseURL(url)
89
- return feed.items.slice(0, count)
90
- } catch (err) {
91
- console.error('Error fetching news:', err.message)
92
- process.exit(1)
93
- }
94
- }
95
-
96
- function wrapText(text, width) {
97
- const lines = []
98
- let start = 0
99
- while (start < text.length) {
100
- let end = Math.min(start + width, text.length)
101
- if (end < text.length && end > start) {
102
- const lastSpace = text.lastIndexOf(' ', end)
103
- if (lastSpace > start && lastSpace > end - width / 2) {
104
- end = lastSpace
105
- }
106
- }
107
- lines.push(text.substring(start, end).trim())
108
- start = end
109
- while (start < text.length && text[start] === ' ') {
110
- start++
111
- }
112
- }
113
- return lines.length ? lines : ['']
114
- }
115
-
116
- function createScreen() {
117
- return blessed.screen({
118
- smartCSR: true,
119
- title: 'Sondakika Haberler'
120
- })
121
- }
122
-
123
- function createMainList(screen, items, sourceName) {
124
- const list = blessed.list({
125
- parent: screen,
126
- top: 2,
127
- left: 0,
128
- width: '100%',
129
- height: '100%-4',
130
- tags: true,
131
- keys: false,
132
- mouse: false,
133
- border: null,
134
- style: {
135
- selected: {
136
- fg: 'white',
137
- bg: 'blue'
138
- }
139
- },
140
- scrollbar: {
141
- ch: '█',
142
- inverse: true
143
- }
144
- })
145
-
146
- list.focus()
147
-
148
- const header = blessed.box({
149
- parent: screen,
150
- top: 0,
151
- left: 0,
152
- width: '100%',
153
- height: 2,
154
- 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ış`,
155
- tags: true,
156
- style: {
157
- fg: 'white',
158
- bg: 'blue'
159
- }
160
- })
161
-
162
- const footer = blessed.box({
163
- parent: screen,
164
- bottom: 0,
165
- left: 0,
166
- width: '100%',
167
- height: 2,
168
- 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ış',
169
- tags: true,
170
- style: {
171
- fg: 'white',
172
- bg: 'black'
173
- }
174
- })
175
-
176
- return { list, header, footer }
177
- }
178
-
179
- function createFullScreenView(screen) {
180
- const overlay = blessed.box({
181
- parent: screen,
182
- top: 0,
183
- left: 0,
184
- width: '100%',
185
- height: '100%',
186
- tags: true,
187
- border: {
188
- type: 'line'
189
- },
190
- style: {
191
- border: {
192
- fg: 'cyan'
193
- }
194
- },
195
- hidden: true,
196
- scrollable: true,
197
- alwaysScroll: true,
198
- scrolloffset: 1
199
- })
200
-
201
- const closeHint = blessed.box({
202
- parent: overlay,
203
- bottom: 0,
204
- left: 0,
205
- width: '100%',
206
- height: 1,
207
- content: '{center}Enter ile listeye dön{/center}',
208
- tags: true,
209
- style: {
210
- fg: 'black',
211
- bg: 'cyan'
212
- }
213
- })
214
-
215
- return { overlay, closeHint }
216
- }
217
-
218
- function updateListDisplay(list, items, page, totalPages) {
219
- const start = page * ITEMS_PER_PAGE
220
- const end = Math.min(start + ITEMS_PER_PAGE, items.length)
221
- const pageItems = items.slice(start, end)
222
-
223
- const lines = pageItems.map((item, idx) => {
224
- const globalIdx = start + idx + 1
225
- const dateStr = formatDate(item.pubDate || item.isodate)
226
- const titleLines = wrapText(item.title, 60)
227
- let line = `{bold}${globalIdx}.{/bold} ${titleLines[0]}`
228
- if (dateStr) {
229
- line += ` {gray-fg}[${dateStr}]{/gray-fg}`
230
- }
231
- return line
232
- })
233
-
234
- list.setItems(lines)
235
- list.scrollTo(0)
236
- }
237
-
238
- function showFullScreen(overlay, item) {
239
- const title = item.title || 'Başlık yok'
240
- const dateStr = formatDate(item.pubDate || item.isodate)
241
- const summary = item.summary || item.contentSnippet || item.content || 'İçerik yok'
242
- const link = item.link || ''
243
-
244
- const titleLines = wrapText(title, 70)
245
- const summaryLines = wrapText(summary, 70)
246
-
247
- let content = '{bold}' + titleLines.join('\n') + '{/bold}\n\n'
248
-
249
- if (dateStr) {
250
- content += `{gray-fg}📅 ${dateStr}{/gray-fg}\n\n`
251
- }
252
-
253
- content += summaryLines.join('\n') + '\n\n'
254
-
255
- if (link) {
256
- content += `{cyan-fg}🔗 ${link}{/cyan-fg}`
257
- }
258
-
259
- overlay.setContent(content)
260
- overlay.show()
261
- }
262
-
263
- function hideFullScreen(overlay) {
264
- overlay.hide()
265
- }
266
-
267
- async function main() {
268
- const args = process.argv.slice(2)
269
- const cliState = loadCLIState()
270
-
271
- if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
272
- const breakingSrc = Object.entries(sources)
273
- .filter(([k, v]) => sourceInfo[k] && sourceInfo[k].isSondakika)
274
- .map(([k, v]) => ` ${k.padEnd(12)} ${sourceInfo[k].name}`)
275
- .join('\n')
276
- const generalSrc = Object.entries(sources)
277
- .filter(([k, v]) => !sourceInfo[k] || !sourceInfo[k].isSondakika)
278
- .map(([k, v]) => ` ${k.padEnd(12)} ${sourceInfo[k] ? sourceInfo[k].name : k}`)
279
- .join('\n')
280
- console.log(`
281
- 📰 Sondakika v${version} - Son dakika haberleri CLI
282
-
283
- Usage:
284
- sondakika <source> [count]
285
- sondakika --version
286
- sondakika --help
287
-
288
- Sources (Son Dakika):
289
- ${breakingSrc}
290
-
291
- Sources (Haberler):
292
- ${generalSrc}
293
-
294
- Examples:
295
- sondakika cumhuriyet # Cumhuriyet haberleri
296
- sondakika trt 15 # TRT 15 haber
297
- sondakika mynet 5 # Mynet 5 haber
298
- sondakika sabah # Sabah haberleri
299
- sondakika haberturk 5 # Habertürk 5 haber
300
- `)
301
- process.exit(0)
302
- }
303
-
304
- if (args[0] === '--version' || args[0] === '-v') {
305
- console.log(`sondakika v${version}`)
306
- process.exit(0)
307
- }
308
-
309
- const source = args[0] || cliState.lastSource
310
- const count = args[1] ? parseInt(args[1]) : cliState.itemsPerPage
311
-
312
- // Save the source and count for next time
313
- cliState.lastSource = source
314
- cliState.itemsPerPage = count
315
- saveCLIState(cliState)
316
-
317
- const sourceKey = source.toLowerCase()
318
- const info = sourceInfo[sourceKey] || { name: source.toUpperCase(), isSondakika: false }
319
- const sourceName = info.isSondakika ? `${info.name} (Son Dakika)` : info.name
320
-
321
- const screen = createScreen()
322
- const { list, header, footer } = createMainList(screen, [], sourceName)
323
- const { overlay, closeHint } = createFullScreenView(screen)
324
-
325
- let items = []
326
- let currentPage = 0
327
- let totalPages = 0
328
- let inFullScreen = false
329
-
330
- const loadNews = async () => {
331
- header.setContent(`{bold}📰 ${sourceName}{/bold} - Yükleniyor...`)
332
- screen.render()
333
-
334
- items = await fetchNews(source, count)
335
- totalPages = Math.ceil(items.length / ITEMS_PER_PAGE)
336
- currentPage = 0
337
-
338
- updateListDisplay(list, items, currentPage, totalPages)
339
- list.select(0)
340
- 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ış`)
341
- 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ış`)
342
- screen.render()
343
- }
344
-
345
- const updateFooter = () => {
346
- 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ış`)
347
- }
348
-
349
- const goToPrevPage = () => {
350
- if (currentPage > 0) {
351
- currentPage--
352
- updateListDisplay(list, items, currentPage, totalPages)
353
- list.select(0)
354
- updateFooter()
355
- screen.render()
356
- }
357
- }
358
-
359
- const goToNextPage = () => {
360
- if (currentPage < totalPages - 1) {
361
- currentPage++
362
- updateListDisplay(list, items, currentPage, totalPages)
363
- list.select(0)
364
- updateFooter()
365
- screen.render()
366
- }
367
- }
368
-
369
- const openFullScreen = () => {
370
- const start = currentPage * ITEMS_PER_PAGE
371
- const itemIndex = start + list.selected
372
- if (itemIndex < items.length) {
373
- showFullScreen(overlay, items[itemIndex])
374
- inFullScreen = true
375
- screen.render()
376
- }
377
- }
378
-
379
- const closeFullScreen = () => {
380
- hideFullScreen(overlay)
381
- inFullScreen = false
382
- screen.render()
383
- }
384
-
385
- screen.key(['up'], () => {
386
- if (!inFullScreen) {
387
- list.up()
388
- screen.render()
389
- }
390
- })
391
-
392
- screen.key(['down'], () => {
393
- if (!inFullScreen) {
394
- list.down()
395
- screen.render()
396
- }
397
- })
398
-
399
- screen.key(['left'], () => {
400
- if (!inFullScreen) {
401
- goToPrevPage()
402
- }
403
- })
404
-
405
- screen.key(['right'], () => {
406
- if (!inFullScreen) {
407
- goToNextPage()
408
- }
409
- })
410
-
411
- screen.key(['enter'], () => {
412
- if (inFullScreen) {
413
- closeFullScreen()
414
- } else {
415
- openFullScreen()
416
- }
417
- })
418
-
419
- // Use Escape, Q, or Ctrl+C to quit
420
- screen.unkey(['q', 'C-c'])
421
- screen.key(['escape', 'q', 'Q', 'C-c'], () => {
422
- process.exit(0)
423
- })
424
-
425
- list.on('select', (el, idx) => {
426
- openFullScreen()
427
- })
428
-
429
- await loadNews()
430
-
431
- screen.render()
432
- }
433
-
434
- main().catch(err => {
435
- console.error('Error:', err.message)
436
- process.exit(1)
437
- })
438
-
@@ -1,12 +0,0 @@
1
- const { contextBridge, ipcRenderer } = require('electron')
2
-
3
- contextBridge.exposeInMainWorld('electronAPI', {
4
- getData: () => ipcRenderer.invoke('article-view-get-data'),
5
- navigate: (callback) => {
6
- ipcRenderer.on('article-view-navigate', (event, data) => callback(data))
7
- },
8
- close: (currentIndex) => ipcRenderer.invoke('article-view-close', currentIndex),
9
- openExternal: (url) => ipcRenderer.invoke('open-external', url),
10
- setTitleFontSize: (size) => ipcRenderer.invoke('set-title-font-size', size),
11
- setContentFontSize: (size) => ipcRenderer.invoke('set-content-font-size', size)
12
- })
@@ -1,20 +0,0 @@
1
- const { app, BrowserWindow, ipcMain, shell } = require('electron')
2
- const path = require('path')
3
- const fs = require('fs')
4
- const Parser = require('rss-parser')
5
-
6
- let mainWindow
7
- let articleWindow = null
8
- let articleWindowData = { newsItems: [], currentIndex: 0 }
9
-
10
- const parser = new Parser({
11
- timeout: 10000,
12
- requestOptions: {
13
- headers: {
14
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
15
- }
16
- },
17
- maxRedirects: 5
18
- })
19
-
20
- const dataDir = path.join(app.getPath('userData'), 'data')
@@ -1,17 +0,0 @@
1
- const { contextBridge, ipcRenderer } = require('electron')
2
-
3
- contextBridge.exposeInMainWorld('electronAPI', {
4
- getSources: () => ipcRenderer.invoke('get-sources'),
5
- getState: () => ipcRenderer.invoke('get-state'),
6
- saveState: (state) => ipcRenderer.invoke('save-state', state),
7
- setFontSize: (size) => ipcRenderer.invoke('set-font-size', size),
8
- setTitleFontSize: (size) => ipcRenderer.invoke('set-title-font-size', size),
9
- setContentFontSize: (size) => ipcRenderer.invoke('set-content-font-size', size),
10
- setTheme: (theme) => ipcRenderer.invoke('set-theme', theme),
11
- fetchNews: (sources, order) => ipcRenderer.invoke('fetch-news', sources, order),
12
- openExternal: (url) => ipcRenderer.invoke('open-external', url),
13
- openArticleView: (items, index) => ipcRenderer.invoke('open-article-view', items, index),
14
- onArticleViewClosed: (callback) => {
15
- ipcRenderer.on('article-view-closed', (event, index) => callback(index))
16
- }
17
- })