turkiyem 1.8.0 → 1.10.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
+ 9 şehrin toplu taşıma verileri, güncel nöbetçi eczaneler, 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
  ---
@@ -52,6 +52,7 @@ Türkiye'de toplu taşıma verileri onlarca farklı belediye sitesi, API ve veri
52
52
 
53
53
  - 🔎 Tarayıcı açmadan **hat ve durak sorgulama**
54
54
  - 📍 Terminal üzerinden **canlı araç takibi** (İstanbul, Bursa)
55
+ - 💊 Anlık **nöbetçi eczane** sorgulama seçenekleri
55
56
  - 🌍 **Deprem bildirimleri** renkli uyarılarla
56
57
  - ⛅ **Hava durumu** ve **hava kalitesi** API key gerektirmeden
57
58
  - 💱 **TCMB döviz kurları** tek komutla
@@ -69,6 +70,10 @@ Türkiye'de toplu taşıma verileri onlarca farklı belediye sitesi, API ve veri
69
70
  | **Antalya** | Antalya Büyükşehir Belediyesi | ✅ | ✅ | — | ✅ |
70
71
  | **Bursa** | Burulaş (Bursakart API) | ✅ | ✅ | ✅ | ✅ |
71
72
  | **İzmir** | ESHOT (GTFS Açık Veri) | ✅ | ✅ | — | ✅ |
73
+ | **Trabzon** | Trabzon Büyükşehir Belediyesi | ✅ | — | — | ✅ |
74
+ | **Samsun** | Samulaş | ✅ | ✅ | — | ✅ |
75
+ | **Mersin** | Mersin Büyükşehir Belediyesi | ✅ | — | — | ✅ |
76
+ | **Kayseri** | Sadece Nöbetçi Eczane | — | — | — | — |
72
77
 
73
78
  > Yeni şehir entegrasyonları için [yol haritasına](#-yol-haritası) bakın.
74
79
 
@@ -76,13 +81,18 @@ Türkiye'de toplu taşıma verileri onlarca farklı belediye sitesi, API ve veri
76
81
 
77
82
  ## ✨ Özellikler
78
83
 
79
- ### 🚌 Toplu Taşıma (6 Şehir)
84
+ ### 🚌 Toplu Taşıma (9 Şehir)
80
85
  - Hat numarası veya adıyla arama
81
86
  - Durak listesi ve sıralı güzergah görüntüleme
82
87
  - Sefer saatleri (gün tipi ve yöne göre)
83
88
  - Durak bazlı geçen hat ve zaman sorgulama
84
89
  - Birden fazla sonuçta interaktif seçim menüsü
85
90
 
91
+ ### 💊 Sağlık & Nöbetçi Eczane
92
+ - **İzmir** ve **Kayseri** için nöbetçi eczane sorgulama
93
+ - İzmir genel eczane arama ve lokasyon bilgisi
94
+ - Telefon numarası ve harita bağlantısı gösterimi
95
+
86
96
  ### 📍 Canlı Araç Takibi
87
97
  - **İstanbul (IETT):** Anlık araç konumu, yön, yakın durak bilgisi
88
98
  - **Bursa (Burulaş):** Plaka, hız, doluluk oranı gösterimi
@@ -183,6 +193,9 @@ turkiyem sehir adana # Şehri Adana olarak ayarla
183
193
  turkiyem sehir antalya # Şehri Antalya olarak ayarla
184
194
  turkiyem sehir bursa # Şehri Bursa olarak ayarla
185
195
  turkiyem sehir izmir # Şehri İzmir olarak ayarla
196
+ turkiyem sehir samsun # Şehri Samsun olarak ayarla
197
+ turkiyem sehir mersin # Şehri Mersin olarak ayarla
198
+ turkiyem sehir kayseri # Şehri Kayseri olarak ayarla
186
199
  ```
187
200
 
188
201
  > 💡 `hat` ve `durak` komutları her zaman seçili şehre göre çalışır.
@@ -213,10 +226,30 @@ turkiyem hat 17
213
226
  # İzmir (ESHOT GTFS) — Hat durakları + sefer saatleri
214
227
  turkiyem sehir izmir
215
228
  turkiyem hat 34
229
+
230
+ # Trabzon — Hat bilgisi + kalkış ve dönüş yönlü sefer saatleri
231
+ turkiyem sehir trabzon
232
+ turkiyem hat 103
233
+
234
+ # Samsun (Samulaş) — Hat bilgisi + duraklar + kalkış saatleri
235
+ turkiyem sehir samsun
236
+ turkiyem hat E1
237
+
238
+ # Mersin — Hat bilgisi + kalkış saatleri
239
+ turkiyem sehir mersin
240
+ turkiyem hat 11M # veya 'merkez' yazarak hat seçimi yapabilirsiniz
216
241
  ```
217
242
 
218
243
  > Birden fazla eşleşen hat varsa interaktif bir seçim menüsü sunulur.
219
244
 
245
+ ### Sağlık & Nöbetçi Eczane (İzmir / Kayseri)
246
+
247
+ ```bash
248
+ turkiyem eczane nobetci # Seçili şehirdeki tüm nöbetçi eczaneleri listeler
249
+ turkiyem eczane nobetci bornova # Seçili şehirde "bornova" ilçesi için arar
250
+ turkiyem eczane ara yusuf # İzmir'de adı/adresi "yusuf" olan tüm eczaneleri getirir
251
+ ```
252
+
220
253
  ### Durak Sorgulama
221
254
 
222
255
  ```bash
@@ -307,6 +340,11 @@ turkiyem temizle # Cache ve yapılandırmayı sıfırla
307
340
  | [Antalya Büyükşehir](https://www.antalya.bel.tr) | Hat / tarife | Antalya | Kamu verisi |
308
341
  | [Burulaş (Bursakart)](https://www.bursakart.com.tr) | Hat / durak / canlı konum | Bursa | Kamu API |
309
342
  | [ESHOT GTFS](https://acikveri.bizizmir.com) | Hat / durak / sefer saatleri | İzmir | İzmir Açık Veri Lisansı |
343
+ | [Trabzon Büyükşehir](https://ulasim.trabzon.bel.tr) | Hat / sefer saatleri | Trabzon | Kamu verisi |
344
+ | [Samulaş](https://samulas.com.tr) | Hat / durak / sefer saatleri | Samsun | Kamu verisi |
345
+ | [Mersin Büyükşehir](https://ulasim.mersin.bel.tr) | Hat / sefer saatleri | Mersin | Kamu API |
346
+ | [İzmir BB Açık Veri](https://acikveri.bizizmir.com) | Eczane Bilgileri | İzmir | İzmir Açık Veri Lisansı |
347
+ | [Kayseri BB Açık Veri](https://acikveri.kayseri.bel.tr) | Nöbetçi Eczaneler | Kayseri | Kamu API |
310
348
  | [Open-Meteo](https://open-meteo.com) | Hava durumu, hava kalitesi | Tüm dünya | CC BY 4.0 |
311
349
  | [TCMB](https://www.tcmb.gov.tr) | Döviz kurları | — | Kamu verisi |
312
350
 
@@ -320,7 +358,7 @@ turkiyem/
320
358
  │ ├── index.js # Commander.js giriş noktası
321
359
  │ ├── commands/
322
360
  │ │ ├── sehir.js # Şehir seçim komutu
323
- │ │ ├── hat.js # Hat sorgulama (6 şehir)
361
+ │ │ ├── hat.js # Hat sorgulama (7 şehir)
324
362
  │ │ ├── durak.js # Durak sorgulama (4 şehir)
325
363
  │ │ ├── deprem.js # AFAD deprem komutları
326
364
  │ │ ├── hava.js # Hava durumu komutları
@@ -334,6 +372,7 @@ turkiyem/
334
372
  │ │ ├── antalyaService.js # Antalya belediye API
335
373
  │ │ ├── bursaService.js # Bursa Burulaş API
336
374
  │ │ ├── izmirService.js # İzmir ESHOT GTFS
375
+ │ │ ├── trabzonService.js # Trabzon belediyesi açık verisi
337
376
  │ │ ├── afadService.js # AFAD deprem API
338
377
  │ │ ├── weatherService.js # Open-Meteo API
339
378
  │ │ └── tcmbService.js # TCMB döviz XML
@@ -462,11 +501,11 @@ Detaylı yol haritası için [`TODO.md`](./TODO.md) dosyasına bakın.
462
501
  |---------|-------|
463
502
  | Kocaeli GTFS Verileri | 📋 Planlandı |
464
503
  | Konya GTFS Verileri | 📋 Planlandı |
465
- | Mersin Ulaşım Tarifeleri | 📋 Planlandı |
466
- | Samsun Otobüs Bilgileri | 📋 Planlandı |
467
- | Trabzon Ulaşım Bilgileri | 📋 Planlandı |
468
- | İzmir Nöbetçi Eczane | 📋 Planlandı |
469
- | Kayseri Nöbetçi Eczane | 📋 Planlandı |
504
+ | Mersin Ulaşım Tarifeleri | Tamamlandı |
505
+ | Samsun Otobüs Bilgileri | Tamamlandı |
506
+ | Trabzon Ulaşım Bilgileri | Tamamlandı |
507
+ | İzmir Nöbetçi Eczane | Tamamlandı |
508
+ | Kayseri Nöbetçi Eczane | Tamamlandı |
470
509
  | e-Nabız / e-Sağlık | 📋 Planlandı |
471
510
 
472
511
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turkiyem",
3
- "version": "1.8.0",
3
+ "version": "1.10.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
+
@@ -2,155 +2,50 @@ import chalk from 'chalk';
2
2
  import prompts from 'prompts';
3
3
  import { printBanner } from '../utils/banner.js';
4
4
  import { getCity } from '../utils/config.js';
5
- import { sehirSec } from './sehir.js';
6
- import { hatSorgula, hatCanliSorgula } from './hat.js';
7
- import { depremSon24, deprem7Gun, depremBuyukluk } from './deprem.js';
8
- import { havaGuncel, havaSaatlik, havaKalitesi } from './hava.js';
9
- import { dovizKurlari } from './doviz.js';
10
- import { durakSorgula } from './durak.js';
11
5
 
12
6
  function printSessionHeader() {
13
7
  const city = getCity();
14
8
  const cityLabel = city ? chalk.green.bold(city) : chalk.yellow('seçilmedi');
15
9
  console.log('');
16
10
  console.log(chalk.gray('─'.repeat(60)));
17
- console.log(chalk.gray(` 🏙️ Aktif şehir: ${cityLabel} │ ${chalk.gray('Çıkmak için: Ctrl+C veya "Çıkış"')}`));
11
+ console.log(chalk.gray(` 🏙️ Aktif şehir: ${cityLabel} │ ${chalk.gray('Çıkmak için: Ctrl+C veya "exit"')}`));
18
12
  console.log(chalk.gray('─'.repeat(60)));
19
13
  console.log('');
20
14
  }
21
15
 
22
16
  export async function showMenu() {
23
17
  printBanner();
24
- console.log(chalk.white.bold(' 🇹🇷 Sürekli oturum modu — İşlem bitince otomatik menüye döner.\n'));
18
+ console.log(chalk.white.bold(' 🇹🇷 Sürekli oturum modu — Komutları direkt yazabilirsiniz (Örn: hat 500T, deprem son24)\n'));
19
+ console.log(chalk.gray(' Tüm komutları görmek için "help" yazabilirsiniz.\n'));
25
20
 
26
21
  // REPL loop — kullanıcı çıkış seçene kadar devam et
27
22
  while (true) {
28
23
  printSessionHeader();
29
24
 
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
- ]
25
+ const { cmd } = await prompts({
26
+ type: 'text',
27
+ name: 'cmd',
28
+ message: chalk.cyan('turkiyem >')
44
29
  });
45
30
 
46
- // Ctrl+C veya Çıkış
47
- if (!response.action || response.action === 'exit') {
31
+ if (cmd === undefined || cmd.trim().toLowerCase() === 'exit' || cmd.trim().toLowerCase() === 'çıkış') {
48
32
  console.log('');
49
33
  console.log(chalk.cyan(' Görüşmek üzere! 🇹🇷👋'));
50
34
  console.log('');
51
35
  break;
52
36
  }
53
37
 
38
+ const args = cmd.trim().split(' ').filter(Boolean);
39
+
40
+ if (args.length === 0) {
41
+ continue;
42
+ }
43
+
54
44
  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
- ]
147
- });
148
- if (sehir) sehirSec(sehir);
149
- break;
150
- }
151
- }
45
+ const { spawnSync } = await import('node:child_process');
46
+ spawnSync(process.argv[0], [process.argv[1], ...args], { stdio: 'inherit' });
152
47
  } catch (err) {
153
- console.log(chalk.red(` Hata: ${err.message}`));
48
+ console.log(chalk.red(`\n Komut çalıştırılamadı: ${err.message}`));
154
49
  }
155
50
  }
156
51
  }
@@ -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
+ }