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 +47 -8
- package/package.json +2 -2
- package/src/commands/eczane.js +94 -0
- package/src/commands/hat.js +196 -1
- package/src/commands/menu.js +17 -122
- package/src/commands/sehir.js +5 -1
- package/src/index.js +19 -0
- package/src/services/eczaneService.js +31 -0
- package/src/services/mersinService.js +112 -0
- package/src/services/samsunService.js +67 -0
- package/src/services/trabzonService.js +70 -0
- package/src/utils/display.js +150 -0
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
14
|
<p align="center">
|
|
15
|
-
|
|
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 (
|
|
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 (
|
|
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 |
|
|
466
|
-
| Samsun Otobüs Bilgileri |
|
|
467
|
-
| Trabzon Ulaşım Bilgileri |
|
|
468
|
-
| İzmir Nöbetçi Eczane |
|
|
469
|
-
| Kayseri Nöbetçi Eczane |
|
|
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.
|
|
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.
|
|
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
|
+
}
|
package/src/commands/hat.js
CHANGED
|
@@ -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
|
|
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
|
+
|
package/src/commands/menu.js
CHANGED
|
@@ -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 "
|
|
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 —
|
|
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
|
|
31
|
-
type: '
|
|
32
|
-
name: '
|
|
33
|
-
message: '
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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(
|
|
48
|
+
console.log(chalk.red(`\n Komut çalıştırılamadı: ${err.message}`));
|
|
154
49
|
}
|
|
155
50
|
}
|
|
156
51
|
}
|
package/src/commands/sehir.js
CHANGED
|
@@ -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
|
+
}
|
package/src/utils/display.js
CHANGED
|
@@ -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
|
+
}
|