turkiyem 1.7.0 → 1.9.0

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 CHANGED
@@ -12,7 +12,7 @@
12
12
  </p>
13
13
 
14
14
  <p align="center">
15
- 6 şehrin toplu taşıma verileri, AFAD deprem bilgileri, Open-Meteo hava durumu, TCMB döviz kurları — hepsi tek bir <code>npm</code> paketi içinde.
15
+ 8 şehrin toplu taşıma verileri, AFAD deprem bilgileri, Open-Meteo hava durumu, TCMB döviz kurları — hepsi tek bir <code>npm</code> paketi içinde.
16
16
  </p>
17
17
 
18
18
  ---
@@ -69,6 +69,8 @@ Türkiye'de toplu taşıma verileri onlarca farklı belediye sitesi, API ve veri
69
69
  | **Antalya** | Antalya Büyükşehir Belediyesi | ✅ | ✅ | — | ✅ |
70
70
  | **Bursa** | Burulaş (Bursakart API) | ✅ | ✅ | ✅ | ✅ |
71
71
  | **İzmir** | ESHOT (GTFS Açık Veri) | ✅ | ✅ | — | ✅ |
72
+ | **Trabzon** | Trabzon Büyükşehir Belediyesi | ✅ | — | — | ✅ |
73
+ | **Samsun** | Samulaş | ✅ | ✅ | — | ✅ |
72
74
 
73
75
  > Yeni şehir entegrasyonları için [yol haritasına](#-yol-haritası) bakın.
74
76
 
@@ -76,7 +78,7 @@ Türkiye'de toplu taşıma verileri onlarca farklı belediye sitesi, API ve veri
76
78
 
77
79
  ## ✨ Özellikler
78
80
 
79
- ### 🚌 Toplu Taşıma (6 Şehir)
81
+ ### 🚌 Toplu Taşıma (8 Şehir)
80
82
  - Hat numarası veya adıyla arama
81
83
  - Durak listesi ve sıralı güzergah görüntüleme
82
84
  - Sefer saatleri (gün tipi ve yöne göre)
@@ -213,6 +215,10 @@ turkiyem hat 17
213
215
  # İzmir (ESHOT GTFS) — Hat durakları + sefer saatleri
214
216
  turkiyem sehir izmir
215
217
  turkiyem hat 34
218
+
219
+ # Trabzon — Hat bilgisi + kalkış ve dönüş yönlü sefer saatleri
220
+ turkiyem sehir trabzon
221
+ turkiyem hat 103
216
222
  ```
217
223
 
218
224
  > Birden fazla eşleşen hat varsa interaktif bir seçim menüsü sunulur.
@@ -307,6 +313,7 @@ turkiyem temizle # Cache ve yapılandırmayı sıfırla
307
313
  | [Antalya Büyükşehir](https://www.antalya.bel.tr) | Hat / tarife | Antalya | Kamu verisi |
308
314
  | [Burulaş (Bursakart)](https://www.bursakart.com.tr) | Hat / durak / canlı konum | Bursa | Kamu API |
309
315
  | [ESHOT GTFS](https://acikveri.bizizmir.com) | Hat / durak / sefer saatleri | İzmir | İzmir Açık Veri Lisansı |
316
+ | [Trabzon Büyükşehir](https://ulasim.trabzon.bel.tr) | Hat / sefer saatleri | Trabzon | Kamu verisi |
310
317
  | [Open-Meteo](https://open-meteo.com) | Hava durumu, hava kalitesi | Tüm dünya | CC BY 4.0 |
311
318
  | [TCMB](https://www.tcmb.gov.tr) | Döviz kurları | — | Kamu verisi |
312
319
 
@@ -320,7 +327,7 @@ turkiyem/
320
327
  │ ├── index.js # Commander.js giriş noktası
321
328
  │ ├── commands/
322
329
  │ │ ├── sehir.js # Şehir seçim komutu
323
- │ │ ├── hat.js # Hat sorgulama (6 şehir)
330
+ │ │ ├── hat.js # Hat sorgulama (7 şehir)
324
331
  │ │ ├── durak.js # Durak sorgulama (4 şehir)
325
332
  │ │ ├── deprem.js # AFAD deprem komutları
326
333
  │ │ ├── hava.js # Hava durumu komutları
@@ -334,6 +341,7 @@ turkiyem/
334
341
  │ │ ├── antalyaService.js # Antalya belediye API
335
342
  │ │ ├── bursaService.js # Bursa Burulaş API
336
343
  │ │ ├── izmirService.js # İzmir ESHOT GTFS
344
+ │ │ ├── trabzonService.js # Trabzon belediyesi açık verisi
337
345
  │ │ ├── afadService.js # AFAD deprem API
338
346
  │ │ ├── weatherService.js # Open-Meteo API
339
347
  │ │ └── tcmbService.js # TCMB döviz XML
@@ -464,7 +472,7 @@ Detaylı yol haritası için [`TODO.md`](./TODO.md) dosyasına bakın.
464
472
  | Konya GTFS Verileri | 📋 Planlandı |
465
473
  | Mersin Ulaşım Tarifeleri | 📋 Planlandı |
466
474
  | Samsun Otobüs Bilgileri | 📋 Planlandı |
467
- | Trabzon Ulaşım Bilgileri | 📋 Planlandı |
475
+ | Trabzon Ulaşım Bilgileri | Tamamlandı |
468
476
  | İzmir Nöbetçi Eczane | 📋 Planlandı |
469
477
  | Kayseri Nöbetçi Eczane | 📋 Planlandı |
470
478
  | e-Nabız / e-Sağlık | 📋 Planlandı |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turkiyem",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "Türkiye Toplu Taşıma ve Deprem CLI aracı - AFAD deprem verileri, EGO hat saatleri ve IETT SOAP/GTFS bilgileri",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -37,7 +37,7 @@
37
37
  "axios": "^1.7.9",
38
38
  "boxen": "^8.0.1",
39
39
  "chalk": "^5.4.1",
40
- "cheerio": "^1.0.0",
40
+ "cheerio": "^1.2.0",
41
41
  "cli-table3": "^0.6.5",
42
42
  "commander": "^13.1.0",
43
43
  "csv-parse": "^5.6.0",
@@ -0,0 +1,94 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { fetchNobetciEczaneler, fetchAllEczaneler, fetchKayseriNobetciEczaneler } from '../services/eczaneService.js';
4
+ import { createNobetciEczaneTable, createEczaneListTable } from '../utils/display.js';
5
+ import { getCity } from '../utils/config.js';
6
+
7
+ function getCityOrCheck() {
8
+ const city = getCity() || 'izmir';
9
+ if (!['izmir', 'kayseri'].includes(city)) {
10
+ console.log(chalk.yellow('Eczane servisi şu an yalnızca İzmir ve Kayseri için desteklenmektedir. Lütfen uygulamanın şehrini değiştirin.'));
11
+ process.exit(0);
12
+ }
13
+ return city;
14
+ }
15
+
16
+ export async function eczaneNobetci(ilce) {
17
+ const city = getCityOrCheck();
18
+ const spinner = ora('Nöbetçi eczaneler alınıyor...').start();
19
+ try {
20
+ let data = [];
21
+ if (city === 'izmir') {
22
+ data = await fetchNobetciEczaneler();
23
+ } else if (city === 'kayseri') {
24
+ const rawData = await fetchKayseriNobetciEczaneler();
25
+ data = rawData.map(e => ({
26
+ Bolge: e.district,
27
+ Adi: e.name,
28
+ Telefon: e.phone,
29
+ Adres: e.address,
30
+ LokasyonX: e.latitude,
31
+ LokasyonY: e.longitude
32
+ }));
33
+ }
34
+ let filtered = data;
35
+ if (ilce) {
36
+ const ilceUpper = ilce.toLocaleUpperCase('tr-TR');
37
+ filtered = data.filter(e => e.Bolge && e.Bolge.toLocaleUpperCase('tr-TR').includes(ilceUpper));
38
+ }
39
+
40
+ spinner.succeed(`Nöbetçi eczane verisi alındı (${filtered.length} sonuç).`);
41
+
42
+ if (filtered.length === 0) {
43
+ console.log(chalk.yellow(`Belirtilen kritere (${ilce || 'tümü'}) uygun nöbetçi eczane bulunamadı.`));
44
+ return;
45
+ }
46
+ console.log('\n' + createNobetciEczaneTable(filtered));
47
+ } catch (error) {
48
+ spinner.fail(chalk.red('Hata: ' + error.message));
49
+ }
50
+ }
51
+
52
+ export async function eczaneAra(kelime) {
53
+ const city = getCityOrCheck();
54
+
55
+ if (city === 'kayseri') {
56
+ console.log(chalk.yellow('Kayseri için şu anda tüm eczanelerde arama desteklenmemektedir (sadece nöbetçi eczaneleri sorgulayabilirsiniz).'));
57
+ return;
58
+ }
59
+
60
+ const spinner = ora('Eczane listesi alınıyor...').start();
61
+ try {
62
+ const data = await fetchAllEczaneler();
63
+ let filtered = data;
64
+ if (kelime) {
65
+ const kw = kelime.toLocaleUpperCase('tr-TR');
66
+ filtered = data.filter(e =>
67
+ (e.Adi && e.Adi.toLocaleUpperCase('tr-TR').includes(kw)) ||
68
+ (e.Bolge && e.Bolge.toLocaleUpperCase('tr-TR').includes(kw)) ||
69
+ (e.Adres && e.Adres.toLocaleUpperCase('tr-TR').includes(kw))
70
+ );
71
+ } else {
72
+ spinner.info(`Sistemde kayıtlı toplam ${data.length} eczane bulunuyor.`);
73
+ console.log(chalk.yellow('Arama yapmak için lütfen bir kelime veya ilçe giriniz:'));
74
+ console.log(chalk.white('Örnek: turkiyem eczane ara Konak\n'));
75
+ return;
76
+ }
77
+
78
+ spinner.succeed(`Eczane listesi alındı (${filtered.length} sonuç).`);
79
+
80
+ if (filtered.length === 0) {
81
+ console.log(chalk.yellow(`"${kelime}" ile eşleşen eczane bulunamadı.`));
82
+ return;
83
+ }
84
+
85
+ if (filtered.length > 50) {
86
+ console.log(chalk.yellow(`\nÇok fazla sonuç bulundu (${filtered.length}). Sadece ilk 50 kayıt gösteriliyor. Daha spesifik bir arama yapabilirsiniz.`));
87
+ filtered = filtered.slice(0, 50);
88
+ }
89
+
90
+ console.log('\n' + createEczaneListTable(filtered));
91
+ } catch (error) {
92
+ spinner.fail(chalk.red('Hata: ' + error.message));
93
+ }
94
+ }
@@ -7,6 +7,9 @@ import { fetchAdanaBuses, fetchAdanaBusDetails } from '../services/adanaService.
7
7
  import { fetchAntalyaFormOptions, fetchAntalyaRouteSchedule } from '../services/antalyaService.js';
8
8
  import { searchBursaRouteAndStation, getBursaRouteStops, getBursaScheduleByStop, getBursaRealTimeLocation } from '../services/bursaService.js';
9
9
  import { searchIzmirRoutes, getIzmirRouteStopsAndSchedule } from '../services/izmirService.js';
10
+ import { fetchTrabzonBuses, fetchTrabzonBusSchedule } from '../services/trabzonService.js';
11
+ import { fetchSamsunBuses, fetchSamsunBusSchedule } from '../services/samsunService.js';
12
+ import { fetchMersinRoutes, fetchMersinSchedule } from '../services/mersinService.js';
10
13
  import prompts from 'prompts';
11
14
  import {
12
15
  createEgoInfoTable,
@@ -23,6 +26,10 @@ import {
23
26
  createBursaLiveTrackingTable,
24
27
  createIzmirStopsTable,
25
28
  createIzmirScheduleTable,
29
+ createTrabzonScheduleTable,
30
+ createSamsunScheduleTable,
31
+ createSamsunStopsTable,
32
+ createMersinScheduleTable,
26
33
  } from '../utils/display.js';
27
34
 
28
35
  export async function hatSorgula(hatNo) {
@@ -52,8 +59,14 @@ export async function hatSorgula(hatNo) {
52
59
  await queryBursa(hatNo);
53
60
  } else if (city === 'izmir') {
54
61
  await queryIzmir(hatNo);
62
+ } else if (city === 'trabzon') {
63
+ await queryTrabzon(hatNo);
64
+ } else if (city === 'samsun') {
65
+ await querySamsun(hatNo);
66
+ } else if (city === 'mersin') {
67
+ await queryMersin(hatNo);
55
68
  } else {
56
- console.log(chalk.red(`Desteklenmeyen şehir: ${city}. ankara, istanbul, adana, antalya, bursa veya izmir seçin.`));
69
+ console.log(chalk.red(`Desteklenmeyen şehir: ${city}. ankara, istanbul, adana, antalya, bursa, izmir, trabzon, samsun veya mersin seçin.`));
57
70
  }
58
71
  }
59
72
 
@@ -427,3 +440,185 @@ async function queryIzmir(hatNo) {
427
440
  console.log(chalk.red(err.message));
428
441
  }
429
442
  }
443
+
444
+ async function queryTrabzon(hatNo) {
445
+ const spinner = ora(`Trabzon hat araması yapılıyor (${hatNo})...`).start();
446
+ try {
447
+ const listData = await fetchTrabzonBuses();
448
+ const query = hatNo.toUpperCase();
449
+ const matches = listData.filter(b => b.name.includes(query) || b.name === query);
450
+
451
+ if (matches.length === 0) {
452
+ spinner.fail(chalk.yellow(`"${hatNo}" aramasıyla eşleşen Trabzon otobüs hattı bulunamadı.`));
453
+ return;
454
+ }
455
+
456
+ spinner.stop();
457
+ let targetId = matches[0].id;
458
+ let selectedName = matches[0].name;
459
+
460
+ if (matches.length > 1) {
461
+ const response = await prompts({
462
+ type: 'select',
463
+ name: 'selectedTarget',
464
+ message: `Birden fazla hat bulundu. Hangisini görmek istersiniz?`,
465
+ choices: matches.map(m => ({ title: m.name, value: m.id }))
466
+ });
467
+ targetId = response.selectedTarget;
468
+ selectedName = matches.find(m => m.id === targetId)?.name;
469
+ if (!targetId) return;
470
+ }
471
+
472
+ spinner.start(`Hat detayları alınıyor (${selectedName})...`);
473
+ const details = await fetchTrabzonBusSchedule(targetId);
474
+ spinner.succeed(`Hat bilgileri alındı`);
475
+ console.log('');
476
+ console.log(chalk.white.bold(` ${details.busName}`));
477
+
478
+ if (details.gidisSaat && details.gidisSaat.length > 0) {
479
+ console.log('');
480
+ console.log(chalk.cyan.bold(` Yön: ${details.direction1}`));
481
+ console.log(createTrabzonScheduleTable(details.gidisSaat));
482
+ }
483
+
484
+ if (details.donusSaat && details.donusSaat.length > 0) {
485
+ console.log('');
486
+ console.log(chalk.cyan.bold(` Yön: ${details.direction2}`));
487
+ console.log(createTrabzonScheduleTable(details.donusSaat));
488
+ }
489
+ } catch (err) {
490
+ if (spinner.isSpinning) spinner.fail(chalk.red('Trabzon ulaşım verisi alınamadı.'));
491
+ console.log(chalk.red(err.message));
492
+ }
493
+ }
494
+
495
+ async function querySamsun(hatNo) {
496
+ const spinner = ora('Samsun (Samulaş) hat bilgileri aranıyor...').start();
497
+ try {
498
+ const buses = await fetchSamsunBuses();
499
+
500
+ // Find matching buses
501
+ const normalizedHatNo = hatNo.toLowerCase().replace(/\s+/g, '');
502
+ let matched = buses.filter(b =>
503
+ b.name.toLowerCase().replace(/\s+/g, '').includes(normalizedHatNo) ||
504
+ b.id.toString() === normalizedHatNo
505
+ );
506
+
507
+ if (matched.length === 0) {
508
+ spinner.fail(`Samsun sisteminde "${hatNo}" aramasına uygun hat bulunamadı.`);
509
+ return;
510
+ }
511
+
512
+ spinner.stop();
513
+
514
+ let selectedLineNo;
515
+
516
+ if (matched.length === 1) {
517
+ selectedLineNo = matched[0].id;
518
+ console.log(chalk.green(`✔ Bulunan hat: ${chalk.bold(matched[0].name)}`));
519
+ } else {
520
+ console.log(chalk.yellow(`Birden fazla hat bulundu ("${hatNo}" için):`));
521
+
522
+ const choices = matched.map(b => ({
523
+ title: chalk.bold(b.name),
524
+ description: `ID: ${b.id}`,
525
+ value: b.id
526
+ }));
527
+
528
+ const response = await prompts({
529
+ type: 'select',
530
+ name: 'selectedBus',
531
+ message: 'Aşağıdaki ilgili hatlardan birini seçin:',
532
+ choices: choices
533
+ });
534
+
535
+ if (!response.selectedBus) {
536
+ console.log(chalk.red('Seçim yapılmadı.'));
537
+ return;
538
+ }
539
+
540
+ selectedLineNo = response.selectedBus;
541
+ }
542
+
543
+ spinner.start('Seçilen hattın saat ve durak bilgileri alınıyor...');
544
+
545
+ const details = await fetchSamsunBusSchedule(selectedLineNo);
546
+
547
+ spinner.succeed('Hat bilgileri alındı\n');
548
+
549
+ console.log(chalk.cyan.bold(` ${details.busName} BİLGİLERİ\n`));
550
+
551
+ if (details.haftaIci.times.length > 0) {
552
+ console.log(chalk.magenta.bold(` Hafta İçi: ${details.haftaIci.title}`));
553
+ console.log(createSamsunScheduleTable(details.haftaIci.times));
554
+ }
555
+ if (details.cumartesi.times.length > 0) {
556
+ console.log(chalk.magenta.bold(` Cumartesi: ${details.cumartesi.title}`));
557
+ console.log(createSamsunScheduleTable(details.cumartesi.times));
558
+ }
559
+ if (details.pazar.times.length > 0) {
560
+ console.log(chalk.magenta.bold(` Pazar: ${details.pazar.title}`));
561
+ console.log(createSamsunScheduleTable(details.pazar.times));
562
+ }
563
+
564
+ if (details.stops && details.stops.length > 0) {
565
+ console.log(chalk.blue.bold(` Güzergah (Duraklar)`));
566
+ console.log(createSamsunStopsTable(details.stops));
567
+ } else {
568
+ console.log(chalk.yellow(' (Durak listesi bulunamadı)'));
569
+ }
570
+
571
+ } catch (error) {
572
+ if (spinner.isSpinning) spinner.fail('Bilgiler alınırken bir hata oluştu.');
573
+ console.log(chalk.red('Hata (Samsun): ' + error.message));
574
+ }
575
+ }
576
+
577
+ async function queryMersin(hatNo) {
578
+ const spinner = ora(`Mersin hat araması yapılıyor (${hatNo})...`).start();
579
+ try {
580
+ const matches = await fetchMersinRoutes(hatNo);
581
+
582
+ if (matches.length === 0) {
583
+ spinner.fail(chalk.yellow(`"${hatNo}" aramasıyla eşleşen Mersin otobüs hattı bulunamadı.`));
584
+ return;
585
+ }
586
+
587
+ spinner.stop();
588
+ let selectedRoute = matches[0];
589
+
590
+ if (matches.length > 1) {
591
+ const response = await prompts({
592
+ type: 'select',
593
+ name: 'route',
594
+ message: 'Birden fazla hat bulundu. Hangisini görmek istersiniz?',
595
+ choices: matches.map(m => ({
596
+ title: `${m.hatNo} - ${m.hatAdi} (${m.bolge})`,
597
+ value: m
598
+ }))
599
+ });
600
+ selectedRoute = response.route;
601
+ if (!selectedRoute) return;
602
+ }
603
+
604
+ spinner.start(`Sefer saatleri alınıyor (${selectedRoute.hatNo} - ${selectedRoute.hatAdi})...`);
605
+
606
+ const schedule = await fetchMersinSchedule(selectedRoute.hatNo);
607
+
608
+ spinner.succeed(`Mersin hat bilgileri alındı`);
609
+ console.log('');
610
+ console.log(chalk.white.bold(` ${selectedRoute.hatNo} - ${selectedRoute.hatAdi} (${selectedRoute.bolge}) Hareket Saatleri`));
611
+
612
+ if (schedule.haftaIci.length > 0 || schedule.cumartesi.length > 0 || schedule.pazar.length > 0) {
613
+ console.log(createMersinScheduleTable(schedule));
614
+ } else {
615
+ console.log(chalk.yellow(' Sefer saati verisi bulunamadı.'));
616
+ }
617
+
618
+ } catch (err) {
619
+ if (spinner.isSpinning) spinner.fail('Mersin verisi alınamadı.');
620
+ console.log(chalk.red(err.message));
621
+ }
622
+ }
623
+
624
+
@@ -1,86 +1,158 @@
1
+ import chalk from 'chalk';
1
2
  import prompts from 'prompts';
2
3
  import { printBanner } from '../utils/banner.js';
4
+ import { getCity } from '../utils/config.js';
3
5
  import { sehirSec } from './sehir.js';
4
- import { hatSorgula } from './hat.js';
5
- import { depremSon24, deprem7Gun } from './deprem.js';
6
- import { havaGuncel, havaSaatlik } from './hava.js';
6
+ import { hatSorgula, hatCanliSorgula } from './hat.js';
7
+ import { depremSon24, deprem7Gun, depremBuyukluk } from './deprem.js';
8
+ import { havaGuncel, havaSaatlik, havaKalitesi } from './hava.js';
7
9
  import { dovizKurlari } from './doviz.js';
8
10
  import { durakSorgula } from './durak.js';
9
11
 
12
+ function printSessionHeader() {
13
+ const city = getCity();
14
+ const cityLabel = city ? chalk.green.bold(city) : chalk.yellow('seçilmedi');
15
+ console.log('');
16
+ console.log(chalk.gray('─'.repeat(60)));
17
+ console.log(chalk.gray(` 🏙️ Aktif şehir: ${cityLabel} │ ${chalk.gray('Çıkmak için: Ctrl+C veya "Çıkış"')}`));
18
+ console.log(chalk.gray('─'.repeat(60)));
19
+ console.log('');
20
+ }
21
+
10
22
  export async function showMenu() {
11
23
  printBanner();
24
+ console.log(chalk.white.bold(' 🇹🇷 Sürekli oturum modu — İşlem bitince otomatik menüye döner.\n'));
12
25
 
13
- const response = await prompts({
14
- type: 'select',
15
- name: 'action',
16
- message: 'Ne yapmak istersin?',
17
- choices: [
18
- { title: '🌍 Deprem Bilgileri (Son 24 Saat)', value: 'deprem24' },
19
- { title: '🌍 Deprem Bilgileri (Son 7 Gün)', value: 'deprem7' },
20
- { title: '⛅ Güncel Hava Durumu', value: 'havaGuncel' },
21
- { title: '⛅ Saatlik Hava Tahmini', value: 'havaSaatlik' },
22
- { title: '🚌 Toplu Taşıma (Hat Sorgula)', value: 'hat' },
23
- { title: '🚏 Durak Sorgula (Adana/Antalya/Bursa/İzmir)', value: 'durak' },
24
- { title: '₺ Döviz Kurları (TCMB)', value: 'doviz' },
25
- { title: '⚙️ Şehir Değiştir', value: 'sehir' },
26
- { title: '❌ Çıkış', value: 'exit' }
27
- ]
28
- });
26
+ // REPL loop kullanıcı çıkış seçene kadar devam et
27
+ while (true) {
28
+ printSessionHeader();
29
29
 
30
- if (!response.action || response.action === 'exit') {
31
- return;
32
- }
30
+ const response = await prompts({
31
+ type: 'select',
32
+ name: 'action',
33
+ message: 'Ne yapmak istersin?',
34
+ choices: [
35
+ { title: '🚌 Toplu Taşıma (Hat Sorgula)', value: 'hat' },
36
+ { title: '📍 Canlı Araç Takibi', value: 'canli' },
37
+ { title: '🚏 Durak Sorgula', value: 'durak' },
38
+ { title: '🌍 Deprem Bilgileri', value: 'deprem' },
39
+ { title: '⛅ Hava Durumu', value: 'hava' },
40
+ { title: '💱 Döviz Kurları (TCMB)', value: 'doviz' },
41
+ { title: '⚙️ Şehir Değiştir', value: 'sehir' },
42
+ { title: '❌ Çıkış', value: 'exit' }
43
+ ]
44
+ });
33
45
 
34
- switch (response.action) {
35
- case 'deprem24':
36
- await depremSon24();
37
- break;
38
- case 'deprem7':
39
- await deprem7Gun();
40
- break;
41
- case 'havaGuncel':
42
- await havaGuncel();
43
- break;
44
- case 'havaSaatlik':
45
- await havaSaatlik(undefined, 2);
46
- break;
47
- case 'hat': {
48
- const { hatNo } = await prompts({
49
- type: 'text',
50
- name: 'hatNo',
51
- message: 'Sorgulamak istediğiniz hat numarasını/adını girin:'
52
- });
53
- if (hatNo) await hatSorgula(hatNo);
54
- break;
55
- }
56
- case 'durak': {
57
- const { stopId } = await prompts({
58
- type: 'text',
59
- name: 'stopId',
60
- message: 'Durak numarasını (Stop ID / Durak No) girin:'
61
- });
62
- if (stopId) await durakSorgula(stopId);
46
+ // Ctrl+C veya Çıkış
47
+ if (!response.action || response.action === 'exit') {
48
+ console.log('');
49
+ console.log(chalk.cyan(' Görüşmek üzere! 🇹🇷👋'));
50
+ console.log('');
63
51
  break;
64
52
  }
65
- case 'doviz':
66
- await dovizKurlari({ tum: false });
67
- break;
68
- case 'sehir': {
69
- const { sehir } = await prompts({
70
- type: 'select',
71
- name: 'sehir',
72
- message: 'Hangi şehri seçmek istersiniz?',
73
- choices: [
74
- { title: 'Ankara', value: 'ankara' },
75
- { title: 'İstanbul', value: 'istanbul' },
76
- { title: 'Adana', value: 'adana' },
77
- { title: 'Antalya', value: 'antalya' },
78
- { title: 'Bursa', value: 'bursa' },
79
- { title: 'İzmir', value: 'izmir' }
80
- ]
81
- });
82
- if (sehir) sehirSec(sehir);
83
- break;
53
+
54
+ try {
55
+ switch (response.action) {
56
+ case 'hat': {
57
+ const { hatNo } = await prompts({
58
+ type: 'text',
59
+ name: 'hatNo',
60
+ message: 'Hat numarasını/adını girin:'
61
+ });
62
+ if (hatNo) await hatSorgula(hatNo);
63
+ break;
64
+ }
65
+ case 'canli': {
66
+ const { hatNo } = await prompts({
67
+ type: 'text',
68
+ name: 'hatNo',
69
+ message: 'Canlı takip için hat numarasını girin:'
70
+ });
71
+ if (hatNo) await hatCanliSorgula(hatNo, {});
72
+ break;
73
+ }
74
+ case 'durak': {
75
+ const { stopId } = await prompts({
76
+ type: 'text',
77
+ name: 'stopId',
78
+ message: 'Durak numarasını/adını girin:'
79
+ });
80
+ if (stopId) await durakSorgula(stopId);
81
+ break;
82
+ }
83
+ case 'deprem': {
84
+ const { subAction } = await prompts({
85
+ type: 'select',
86
+ name: 'subAction',
87
+ message: 'Hangi deprem verisi?',
88
+ choices: [
89
+ { title: '🕐 Son 24 Saat', value: 'son24' },
90
+ { title: '📅 Son 7 Gün', value: '7gun' },
91
+ { title: '📊 Büyüklüğe Göre Filtrele', value: 'buyukluk' },
92
+ { title: '↩ Geri', value: 'back' }
93
+ ]
94
+ });
95
+ if (subAction === 'son24') await depremSon24();
96
+ else if (subAction === '7gun') await deprem7Gun();
97
+ else if (subAction === 'buyukluk') {
98
+ const { deger } = await prompts({
99
+ type: 'text',
100
+ name: 'deger',
101
+ message: 'Minimum büyüklük değeri (ör: 4.0):'
102
+ });
103
+ if (deger) await depremBuyukluk(deger);
104
+ }
105
+ break;
106
+ }
107
+ case 'hava': {
108
+ const { subAction } = await prompts({
109
+ type: 'select',
110
+ name: 'subAction',
111
+ message: 'Hangi hava verisi?',
112
+ choices: [
113
+ { title: '🌡️ Güncel Hava', value: 'guncel' },
114
+ { title: '⏱️ Saatlik Tahmin', value: 'saatlik' },
115
+ { title: '🏭 Hava Kalitesi', value: 'kalite' },
116
+ { title: '↩ Geri', value: 'back' }
117
+ ]
118
+ });
119
+ if (subAction === 'back') break;
120
+ const { konum } = await prompts({
121
+ type: 'text',
122
+ name: 'konum',
123
+ message: 'Şehir adı veya koordinat (boş bırakırsan seçili şehir):'
124
+ });
125
+ const loc = konum || undefined;
126
+ if (subAction === 'guncel') await havaGuncel(loc);
127
+ else if (subAction === 'saatlik') await havaSaatlik(loc, 2);
128
+ else if (subAction === 'kalite') await havaKalitesi(loc);
129
+ break;
130
+ }
131
+ case 'doviz':
132
+ await dovizKurlari({ tum: false });
133
+ break;
134
+ case 'sehir': {
135
+ const { sehir } = await prompts({
136
+ type: 'select',
137
+ name: 'sehir',
138
+ message: 'Hangi şehri seçmek istersiniz?',
139
+ choices: [
140
+ { title: 'Ankara', value: 'ankara' },
141
+ { title: 'İstanbul', value: 'istanbul' },
142
+ { title: 'Adana', value: 'adana' },
143
+ { title: 'Antalya', value: 'antalya' },
144
+ { title: 'Bursa', value: 'bursa' },
145
+ { title: 'İzmir', value: 'izmir' },
146
+ { title: 'Trabzon', value: 'trabzon' },
147
+ { title: 'Samsun', value: 'samsun' }
148
+ ]
149
+ });
150
+ if (sehir) sehirSec(sehir);
151
+ break;
152
+ }
153
+ }
154
+ } catch (err) {
155
+ console.log(chalk.red(` Hata: ${err.message}`));
84
156
  }
85
157
  }
86
158
  }
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import { setCity, getCity } from '../utils/config.js';
3
3
 
4
- const SUPPORTED_CITIES = ['ankara', 'istanbul', 'adana', 'antalya', 'bursa', 'izmir'];
4
+ const SUPPORTED_CITIES = ['ankara', 'istanbul', 'adana', 'antalya', 'bursa', 'izmir', 'trabzon', 'samsun', 'mersin', 'kayseri'];
5
5
 
6
6
  export function sehirSec(city) {
7
7
  if (!city) {
@@ -19,6 +19,10 @@ export function sehirSec(city) {
19
19
  console.log(chalk.cyan(' turkiyem sehir antalya'));
20
20
  console.log(chalk.cyan(' turkiyem sehir bursa'));
21
21
  console.log(chalk.cyan(' turkiyem sehir izmir'));
22
+ console.log(chalk.cyan(' turkiyem sehir trabzon'));
23
+ console.log(chalk.cyan(' turkiyem sehir samsun'));
24
+ console.log(chalk.cyan(' turkiyem sehir mersin'));
25
+ console.log(chalk.cyan(' turkiyem sehir kayseri'));
22
26
  return;
23
27
  }
24
28
 
package/src/index.js CHANGED
@@ -11,6 +11,7 @@ import { depremSon24, deprem7Gun, depremBuyukluk } from './commands/deprem.js';
11
11
  import { havaGuncel, havaKalitesi, havaSaatlik } from './commands/hava.js';
12
12
  import { temizle } from './commands/temizle.js';
13
13
  import { dovizKurlari } from './commands/doviz.js';
14
+ import { eczaneNobetci, eczaneAra } from './commands/eczane.js';
14
15
 
15
16
  const require = createRequire(import.meta.url);
16
17
  const pkg = require('../package.json');
@@ -115,6 +116,24 @@ havaCmd
115
116
  await havaKalitesi(sehirVeyaKoordinat);
116
117
  });
117
118
 
119
+ const eczaneCmd = program
120
+ .command('eczane')
121
+ .description('Eczane işlemleri (İzmir ve Kayseri)');
122
+
123
+ eczaneCmd
124
+ .command('nobetci [ilce]')
125
+ .description('Nöbetçi eczaneleri sorgula (İlçe filtreli)')
126
+ .action(async (ilce) => {
127
+ await eczaneNobetci(ilce);
128
+ });
129
+
130
+ eczaneCmd
131
+ .command('ara [kelime]')
132
+ .description('Tüm eczanelerde kelime veya ilçeye göre arama yap')
133
+ .action(async (kelime) => {
134
+ await eczaneAra(kelime);
135
+ });
136
+
118
137
  program
119
138
  .command('temizle')
120
139
  .description('Cache ve yapılandırmayı temizle')
@@ -0,0 +1,31 @@
1
+ import axios from 'axios';
2
+
3
+ export async function fetchNobetciEczaneler() {
4
+ const url = 'https://openapi.izmir.bel.tr/api/ibb/nobetcieczaneler';
5
+ const response = await axios.get(url, {
6
+ headers: {
7
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
8
+ }
9
+ });
10
+ return response.data;
11
+ }
12
+
13
+ export async function fetchAllEczaneler() {
14
+ const url = 'https://openapi.izmir.bel.tr/api/ibb/eczaneler';
15
+ const response = await axios.get(url, {
16
+ headers: {
17
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
18
+ }
19
+ });
20
+ return response.data;
21
+ }
22
+
23
+ export async function fetchKayseriNobetciEczaneler() {
24
+ const url = 'https://acikveri.kayseri.bel.tr/api/kbb/eczane';
25
+ const response = await axios.get(url, {
26
+ headers: {
27
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
28
+ }
29
+ });
30
+ return response.data;
31
+ }
@@ -0,0 +1,112 @@
1
+ import axios from 'axios';
2
+ import https from 'https';
3
+
4
+ const MERSIN_AJAX_URL = 'https://ulasim.mersin.bel.tr/ajax/bilgi.php';
5
+
6
+ // Mersin sertifika hatalarını yok saymak için (Bazen belediye sitelerinde oluyor)
7
+ const httpsAgent = new https.Agent({ rejectUnauthorized: false });
8
+
9
+ export async function fetchMersinRoutes(regionOrKeyword) {
10
+ let requestKeyword = 'TUM';
11
+
12
+ // Eğer bölgesel sorguysa (MERKEZ, TARSUS vb.)
13
+ const regions = ['MERKEZ', 'TARSUS', 'GÜLNAR', 'ANAMUR', 'KÖYLER'];
14
+ if (regionOrKeyword && regions.includes(regionOrKeyword.toUpperCase())) {
15
+ requestKeyword = regionOrKeyword.toUpperCase();
16
+ }
17
+
18
+ const params = new URLSearchParams();
19
+ params.append('aranan', requestKeyword);
20
+ params.append('tipi', 'hatbilgisi');
21
+
22
+ const response = await axios.post(MERSIN_AJAX_URL, params, {
23
+ headers: {
24
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
25
+ 'User-Agent': 'Mozilla/5.0'
26
+ },
27
+ httpsAgent
28
+ });
29
+
30
+ if (!response.data || !Array.isArray(response.data)) {
31
+ return [];
32
+ }
33
+
34
+ let routes = response.data.map(r => {
35
+ const hNo = r.hat_no && r.hat_no['0'] ? r.hat_no['0'].trim() : '';
36
+ const hAd = r.hat_adi && r.hat_adi['0'] ? r.hat_adi['0'].trim() : '';
37
+ const hYon = r.hat_yon && r.hat_yon['0'] ? r.hat_yon['0'].trim() : '';
38
+ const bolge = r.bolge && r.bolge['0'] ? r.bolge['0'].trim() : '';
39
+
40
+ return {
41
+ hatNo: hNo,
42
+ hatAdi: hAd,
43
+ yon: hYon,
44
+ bolge: bolge
45
+ };
46
+ });
47
+
48
+ // Sadece "G" yönünü (gidiş) alıp filtrelenmiş gösterelim
49
+ routes = routes.filter(r => r.yon === 'G');
50
+
51
+ // Eğer parametre bölge değil de spesifik aramaysa lokalde filtreleyelim
52
+ if (regionOrKeyword && requestKeyword === 'TUM') {
53
+ const kw = regionOrKeyword.toLocaleUpperCase('tr-TR');
54
+ routes = routes.filter(r =>
55
+ (r.hatNo && r.hatNo.toLocaleUpperCase('tr-TR').includes(kw)) ||
56
+ (r.hatAdi && r.hatAdi.toLocaleUpperCase('tr-TR').includes(kw))
57
+ );
58
+ }
59
+
60
+ // Aynı hatta sahip birden fazla kayıt gelirse dedup
61
+ const uniqueMap = new Map();
62
+ routes.forEach(r => uniqueMap.set(r.hatNo, r));
63
+
64
+ return Array.from(uniqueMap.values());
65
+ }
66
+
67
+ export async function fetchMersinSchedule(hatNo) {
68
+ // API "-G" ile çalışıyor genelde
69
+ let queryHat = hatNo;
70
+ if (!queryHat.endsWith('-G')) {
71
+ queryHat = queryHat + '-G';
72
+ }
73
+
74
+ const params = new URLSearchParams();
75
+ params.append('hat_no', queryHat);
76
+ params.append('tipi', 'tarifeler');
77
+
78
+ const response = await axios.post(MERSIN_AJAX_URL, params, {
79
+ headers: {
80
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
81
+ 'User-Agent': 'Mozilla/5.0'
82
+ },
83
+ httpsAgent
84
+ });
85
+
86
+ if (!response.data || !Array.isArray(response.data)) {
87
+ return [];
88
+ }
89
+
90
+ const schedule = {
91
+ haftaIci: [],
92
+ cumartesi: [],
93
+ pazar: []
94
+ };
95
+
96
+ response.data.forEach(item => {
97
+ const gun = item.tarife_gun && item.tarife_gun['0'] ? item.tarife_gun['0'].trim() : '';
98
+ const saat = item.saat && item.saat['0'] ? item.saat['0'].replace(/\n/g, '').trim() : '';
99
+
100
+ if (saat) {
101
+ if (gun.toLocaleUpperCase('tr-TR') === 'HAFTAİÇİ' || gun.toLocaleUpperCase('tr-TR') === 'HAFTA ICI' || gun.toLocaleUpperCase('tr-TR') === 'HAFTA İÇİ') {
102
+ schedule.haftaIci.push(saat);
103
+ } else if (gun.toLocaleUpperCase('tr-TR') === 'CUMARTESİ') {
104
+ schedule.cumartesi.push(saat);
105
+ } else if (gun.toLocaleUpperCase('tr-TR') === 'PAZAR') {
106
+ schedule.pazar.push(saat);
107
+ }
108
+ }
109
+ });
110
+
111
+ return schedule;
112
+ }
@@ -0,0 +1,67 @@
1
+ import axios from 'axios';
2
+ import * as cheerio from 'cheerio';
3
+
4
+ const BASE_URL = 'https://samulas.com.tr';
5
+
6
+ /**
7
+ * Fetches the list of all Samsun buses.
8
+ * Returns an array of objects: { id: string|number, name: string }
9
+ */
10
+ export async function fetchSamsunBuses() {
11
+ const res = await axios.get(`${BASE_URL}/api/v1/lines/list?page=1&limit=500`);
12
+ const data = res.data?.data?.data || [];
13
+
14
+ return data.map(item => ({
15
+ id: item.line_no,
16
+ name: item.text.replace(/\s+/g, ' ').trim()
17
+ }));
18
+ }
19
+
20
+ /**
21
+ * Fetches the schedule of a specific Samsun bus.
22
+ * Returns an object with schedule details and stops.
23
+ */
24
+ export async function fetchSamsunBusSchedule(lineNo) {
25
+ const res = await axios.get(`${BASE_URL}/otobus-detay/${lineNo}`);
26
+ const $ = cheerio.load(res.data);
27
+
28
+ const rootTitle = $('div.page-heading h1').text().trim();
29
+ if (!rootTitle) {
30
+ throw new Error('Saat bilgisi bulunamadı.');
31
+ }
32
+
33
+ const parseTimes = (tabId) => {
34
+ const title = $(`#${tabId} .row.border`).eq(0).text().trim() || '';
35
+ const times = [];
36
+ $(`#${tabId} .row.border`).eq(1).find('div').each((i, el) => {
37
+ const timeStr = $(el).text().replace('*', '').trim();
38
+ if (timeStr && timeStr.includes(':')) times.push(timeStr);
39
+ });
40
+
41
+ // Let's sort the times logically
42
+ times.sort();
43
+
44
+ return { title, times };
45
+ };
46
+
47
+ const haftaIci = parseTimes('haftaIciContent');
48
+ const cumartesi = parseTimes('cumartesiContent');
49
+ const pazar = parseTimes('pazarContent');
50
+
51
+ const stops = [];
52
+ const markerScript = $('script').filter((i, el) => $(el).html().includes('L.marker')).html() || '';
53
+ const matches = [...markerScript.matchAll(/marker\.bindPopup\("<b>(.*?)<\/b>"\)/g)];
54
+
55
+ matches.forEach(m => {
56
+ // e.g., "1 - 11872 - YENİ CEZAEVİ" -> name
57
+ stops.push(m[1]);
58
+ });
59
+
60
+ return {
61
+ busName: rootTitle,
62
+ haftaIci,
63
+ cumartesi,
64
+ pazar,
65
+ stops
66
+ };
67
+ }
@@ -0,0 +1,70 @@
1
+ import axios from 'axios';
2
+ import * as cheerio from 'cheerio';
3
+
4
+ const BASE_URL = 'https://ulasim.trabzon.bel.tr';
5
+
6
+ /**
7
+ * Fetches the list of all Trabzon buses.
8
+ * Returns an array of objects: { id: string, name: string }
9
+ */
10
+ export async function fetchTrabzonBuses() {
11
+ const res = await axios.get(`${BASE_URL}/`);
12
+ const $ = cheerio.load(res.data);
13
+
14
+ const buses = [];
15
+ $('a[href^="/Web/Mobil?hatIdler="]').each((i, el) => {
16
+ const text = $(el).text().trim();
17
+ const href = $(el).attr('href');
18
+ const match = href.match(/hatIdler=(\d+)/);
19
+ if (match && text) {
20
+ buses.push({
21
+ id: match[1],
22
+ name: text.replace(/\s+/g, ' ')
23
+ });
24
+ }
25
+ });
26
+
27
+ return buses;
28
+ }
29
+
30
+ /**
31
+ * Fetches the schedule of a specific Trabzon bus.
32
+ * Returns an object with schedule details.
33
+ */
34
+ export async function fetchTrabzonBusSchedule(hatId) {
35
+ const res = await axios.get(`${BASE_URL}/Web/HatSaat?hatIdler=${hatId}&yon=1`);
36
+ const $ = cheerio.load(res.data);
37
+
38
+ const rootTitle = $('h2').eq(0).text().trim();
39
+ const kalkan1Adi = $('h2').eq(1).text().trim();
40
+ const kalkan2Adi = $('h2').eq(2).text().trim();
41
+
42
+ if ($('#tableGelis').length === 0 && $('#tableGidis').length === 0) {
43
+ throw new Error('Saat bilgisi bulunamadı.');
44
+ }
45
+
46
+ const parseTable = (selector) => {
47
+ const result = [];
48
+ $(selector).find('tbody tr').each((i, el) => {
49
+ const cols = $(el).find('td');
50
+ const haftaIci = $(cols[0]).text().trim();
51
+ const cumartesi = $(cols[1]).text().trim();
52
+ const pazar = $(cols[2]).text().trim();
53
+ if (haftaIci || cumartesi || pazar) {
54
+ result.push({ haftaIci, cumartesi, pazar });
55
+ }
56
+ });
57
+ return result;
58
+ };
59
+
60
+ const gidisSaat = parseTable('#tableGelis');
61
+ const donusSaat = parseTable('#tableGidis');
62
+
63
+ return {
64
+ busName: rootTitle,
65
+ direction1: kalkan1Adi,
66
+ direction2: kalkan2Adi,
67
+ gidisSaat,
68
+ donusSaat
69
+ };
70
+ }
@@ -535,3 +535,153 @@ export function createIzmirStopScheduleTable(schedule) {
535
535
 
536
536
  return table.toString();
537
537
  }
538
+
539
+ export function createTrabzonScheduleTable(schedule) {
540
+ const table = new Table({
541
+ head: [
542
+ chalk.white.bold('Hafta İçi'),
543
+ chalk.white.bold('Cumartesi'),
544
+ chalk.white.bold('Pazar')
545
+ ],
546
+ colWidths: [20, 20, 20],
547
+ style: { head: [], border: ['gray'] },
548
+ wordWrap: true,
549
+ });
550
+
551
+ for (const row of schedule) {
552
+ table.push([
553
+ row.haftaIci || '-',
554
+ row.cumartesi || '-',
555
+ row.pazar || '-'
556
+ ]);
557
+ }
558
+
559
+ return table.toString();
560
+ }
561
+
562
+ export function createSamsunScheduleTable(times) {
563
+ const table = new Table({
564
+ head: [chalk.white.bold('Sefer Saatleri')],
565
+ colWidths: [70],
566
+ style: { head: [], border: ['gray'] },
567
+ wordWrap: true,
568
+ });
569
+
570
+ if (times && times.length > 0) {
571
+ const chunks = [];
572
+ for (let i = 0; i < times.length; i += 10) {
573
+ chunks.push(times.slice(i, i + 10).join(', '));
574
+ }
575
+ table.push([chunks.join('\n')]);
576
+ } else {
577
+ table.push(['-']);
578
+ }
579
+
580
+ return table.toString();
581
+ }
582
+
583
+ export function createSamsunStopsTable(stops) {
584
+ const table = new Table({
585
+ head: [chalk.white.bold('Durak Adı / Bilgisi')],
586
+ colWidths: [70],
587
+ style: { head: [], border: ['gray'] },
588
+ wordWrap: true,
589
+ });
590
+
591
+ for (const stop of stops) {
592
+ table.push([stop]);
593
+ }
594
+
595
+ return table.toString();
596
+ }
597
+
598
+ export function createMersinScheduleTable(schedule) {
599
+ const table = new Table({
600
+ head: [
601
+ chalk.white.bold('Hafta İçi'),
602
+ chalk.white.bold('Cumartesi'),
603
+ chalk.white.bold('Pazar')
604
+ ],
605
+ colWidths: [20, 20, 20],
606
+ style: { head: [], border: ['gray'] },
607
+ wordWrap: true,
608
+ });
609
+
610
+ const maxLen = Math.max(
611
+ schedule.haftaIci.length,
612
+ schedule.cumartesi.length,
613
+ schedule.pazar.length
614
+ );
615
+
616
+ for (let i = 0; i < maxLen; i++) {
617
+ table.push([
618
+ schedule.haftaIci[i] || '-',
619
+ schedule.cumartesi[i] || '-',
620
+ schedule.pazar[i] || '-'
621
+ ]);
622
+ }
623
+
624
+ return table.toString();
625
+ }
626
+
627
+ export function createNobetciEczaneTable(eczaneler) {
628
+ const table = new Table({
629
+ head: [
630
+ chalk.white.bold('İlçe'),
631
+ chalk.white.bold('Eczane Adı'),
632
+ chalk.white.bold('Telefon'),
633
+ chalk.white.bold('Adres'),
634
+ chalk.white.bold('Harita')
635
+ ],
636
+ colWidths: [12, 20, 14, 35, 30],
637
+ style: { head: [], border: ['gray'] },
638
+ wordWrap: true,
639
+ });
640
+
641
+ for (const ecz of eczaneler) {
642
+ let mapLink = '-';
643
+ if (ecz.LokasyonX && ecz.LokasyonY) {
644
+ mapLink = `https://www.google.com/maps/search/?api=1&query=${ecz.LokasyonX},${ecz.LokasyonY}`;
645
+ }
646
+ table.push([
647
+ ecz.Bolge || '-',
648
+ chalk.cyan(ecz.Adi || '-'),
649
+ ecz.Telefon || '-',
650
+ ecz.Adres || '-',
651
+ chalk.blue.underline(mapLink)
652
+ ]);
653
+ }
654
+
655
+ return table.toString();
656
+ }
657
+
658
+ export function createEczaneListTable(eczaneler) {
659
+ const table = new Table({
660
+ head: [
661
+ chalk.white.bold('İlçe'),
662
+ chalk.white.bold('Eczane Adı'),
663
+ chalk.white.bold('Telefon'),
664
+ chalk.white.bold('Adres'),
665
+ chalk.white.bold('Harita')
666
+ ],
667
+ colWidths: [12, 20, 14, 35, 30],
668
+ style: { head: [], border: ['gray'] },
669
+ wordWrap: true,
670
+ });
671
+
672
+ for (const ecz of eczaneler) {
673
+ let mapLink = '-';
674
+ if (ecz.LokasyonX && ecz.LokasyonY) {
675
+ mapLink = `https://www.google.com/maps/search/?api=1&query=${ecz.LokasyonX},${ecz.LokasyonY}`;
676
+ }
677
+ table.push([
678
+ ecz.Bolge || '-',
679
+ chalk.cyan(ecz.Adi || '-'),
680
+ ecz.Telefon || '-',
681
+ ecz.Adres || '-',
682
+ chalk.blue.underline(mapLink)
683
+ ]);
684
+ }
685
+
686
+ return table.toString();
687
+ }