turkiyem 1.4.0 → 1.6.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 +168 -73
- package/package.json +2 -2
- package/src/commands/doviz.js +26 -0
- package/src/commands/hat.js +45 -1
- package/src/index.js +24 -6
- package/src/services/iettService.js +136 -0
- package/src/services/tcmbService.js +47 -0
- package/src/utils/banner.js +2 -0
- package/src/utils/cache.js +1 -0
- package/src/utils/display.js +74 -0
package/README.md
CHANGED
|
@@ -1,17 +1,59 @@
|
|
|
1
1
|
# turkiyem
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
AFAD deprem verileri, EGO (Ankara)
|
|
6
|
-
|
|
3
|
+
Türkiye için modern, terminal tabanlı bir **toplu taşıma + deprem + hava durumu** CLI aracı.
|
|
4
|
+
|
|
5
|
+
`turkiyem`; AFAD deprem verileri, EGO (Ankara) hat saatleri, IETT (İstanbul) hat bilgileri / planlanan sefer saatleri / canlı konum akışları ve Open-Meteo hava verilerini tek komutta toplar.
|
|
6
|
+
|
|
7
|
+
## İçindekiler
|
|
8
|
+
|
|
9
|
+
- [Özellikler](#özellikler)
|
|
10
|
+
- [Kurulum](#kurulum)
|
|
11
|
+
- [Hızlı Başlangıç](#hızlı-başlangıç)
|
|
12
|
+
- [Komut Referansı](#komut-referansı)
|
|
13
|
+
- [Veri Kaynakları](#veri-kaynakları)
|
|
14
|
+
- [Mimari ve Proje Yapısı](#mimari-ve-proje-yapısı)
|
|
15
|
+
- [Yapılandırma ve Cache](#yapılandırma-ve-cache)
|
|
16
|
+
- [Hata Yönetimi](#hata-yönetimi)
|
|
17
|
+
- [Geliştirme](#geliştirme)
|
|
18
|
+
- [Yayınlama (npm)](#yayınlama-npm)
|
|
19
|
+
- [Sık Karşılaşılan Sorunlar](#sık-karşılaşılan-sorunlar)
|
|
20
|
+
- [Lisans](#lisans)
|
|
21
|
+
|
|
22
|
+
## Özellikler
|
|
23
|
+
|
|
24
|
+
- **Deprem (AFAD):**
|
|
25
|
+
- Son 24 saat / son 7 gün deprem listesi
|
|
26
|
+
- Büyüklüğe göre filtreleme
|
|
27
|
+
- `>= 4.0` depremler için renkli vurgu
|
|
28
|
+
- **Toplu taşıma (Ankara / EGO):**
|
|
29
|
+
- Hat bilgileri
|
|
30
|
+
- Gün tipine göre sefer saatleri (Hafta içi / Cumartesi / Pazar)
|
|
31
|
+
- **Toplu taşıma (İstanbul / IETT):**
|
|
32
|
+
- GTFS tabanlı hat özeti
|
|
33
|
+
- SOAP tabanlı planlanan sefer saatleri
|
|
34
|
+
- SOAP başarısız olduğunda GTFS özete otomatik fallback
|
|
35
|
+
- `hat canli` ile canlı araç konumu (servis erişimine bağlı)
|
|
36
|
+
- **Hava durumu (Open-Meteo):**
|
|
37
|
+
- API key gerektirmeden güncel hava
|
|
38
|
+
- Saatlik tahmin (1-7 gün)
|
|
39
|
+
- Hava kalitesi (PM10, PM2.5, CO, NO2)
|
|
40
|
+
- **Ekonomi:**
|
|
41
|
+
- TCMB güncel döviz kurları listesi
|
|
42
|
+
- **CLI UX:**
|
|
43
|
+
- Komut bazlı spinner
|
|
44
|
+
- Tablo tabanlı okunabilir terminal çıktısı
|
|
45
|
+
- Global şehir ayarı (`~/.turkiyem/config.json`)
|
|
46
|
+
- Bellek içi cache ile performans optimizasyonu
|
|
7
47
|
|
|
8
48
|
## Kurulum
|
|
9
49
|
|
|
50
|
+
### Global (önerilen)
|
|
51
|
+
|
|
10
52
|
```bash
|
|
11
53
|
npm install -g turkiyem
|
|
12
54
|
```
|
|
13
55
|
|
|
14
|
-
|
|
56
|
+
### Yerel geliştirme
|
|
15
57
|
|
|
16
58
|
```bash
|
|
17
59
|
git clone <repo-url>
|
|
@@ -20,144 +62,197 @@ npm install
|
|
|
20
62
|
npm link
|
|
21
63
|
```
|
|
22
64
|
|
|
23
|
-
|
|
65
|
+
### Gereksinim
|
|
66
|
+
|
|
67
|
+
- Node.js `20+`
|
|
24
68
|
|
|
25
|
-
|
|
69
|
+
## Hızlı Başlangıç
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
turkiyem
|
|
73
|
+
turkiyem sehir istanbul
|
|
74
|
+
turkiyem hat 34AS
|
|
75
|
+
turkiyem deprem son24
|
|
76
|
+
turkiyem hava guncel
|
|
77
|
+
turkiyem doviz
|
|
78
|
+
```
|
|
26
79
|
|
|
27
|
-
##
|
|
80
|
+
## Komut Referansı
|
|
28
81
|
|
|
29
|
-
###
|
|
82
|
+
### Genel
|
|
30
83
|
|
|
31
84
|
```bash
|
|
32
85
|
turkiyem
|
|
33
86
|
turkiyem help
|
|
87
|
+
turkiyem --version
|
|
88
|
+
turkiyem temizle
|
|
34
89
|
```
|
|
35
90
|
|
|
36
|
-
###
|
|
37
|
-
|
|
38
|
-
Hat sorgulama icin once sehir secmelisiniz:
|
|
91
|
+
### Şehir seçimi
|
|
39
92
|
|
|
40
93
|
```bash
|
|
41
94
|
turkiyem sehir ankara
|
|
42
95
|
turkiyem sehir istanbul
|
|
43
96
|
```
|
|
44
97
|
|
|
45
|
-
|
|
98
|
+
> `hat` komutları seçili şehre göre çalışır.
|
|
46
99
|
|
|
47
|
-
|
|
100
|
+
### Hat sorgulama
|
|
48
101
|
|
|
49
102
|
```bash
|
|
50
103
|
# Ankara (EGO)
|
|
51
104
|
turkiyem sehir ankara
|
|
52
105
|
turkiyem hat 340
|
|
53
106
|
|
|
54
|
-
#
|
|
107
|
+
# İstanbul (IETT)
|
|
55
108
|
turkiyem sehir istanbul
|
|
56
109
|
turkiyem hat 34AS
|
|
57
110
|
```
|
|
58
111
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
112
|
+
İstanbul akışı:
|
|
113
|
+
- 1) GTFS hat özeti
|
|
114
|
+
- 2) SOAP planlanan sefer saatleri
|
|
115
|
+
- 3) SOAP erişilemezse GTFS özeti + bilgilendirme mesajı
|
|
116
|
+
|
|
117
|
+
### IETT canlı konum
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# Özet çıktı
|
|
121
|
+
turkiyem hat canli 34AS
|
|
62
122
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
- SOAP hatasinda: yalnizca GTFS ozet + uyari
|
|
123
|
+
# Detay çıktı
|
|
124
|
+
turkiyem hat canli 34AS --detay
|
|
125
|
+
```
|
|
67
126
|
|
|
68
|
-
|
|
127
|
+
> Not: Canlı konum sadece `istanbul` şehir seçiliyken anlamlıdır ve servis erişimine bağlıdır.
|
|
69
128
|
|
|
70
|
-
|
|
129
|
+
### Deprem (AFAD)
|
|
71
130
|
|
|
72
131
|
```bash
|
|
73
|
-
# Son 24 saat
|
|
74
132
|
turkiyem deprem son24
|
|
75
|
-
|
|
76
|
-
# Son 7 gun
|
|
77
133
|
turkiyem deprem 7gun
|
|
78
|
-
|
|
79
|
-
# Buyukluge gore filtrele (ornegin >= 4.0)
|
|
80
134
|
turkiyem deprem buyukluk 4.0
|
|
81
135
|
```
|
|
82
136
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
### Hava Durumu ve Hava Kalitesi
|
|
86
|
-
|
|
87
|
-
Open-Meteo uzerinden API key gerektirmeden sorgu yapar:
|
|
137
|
+
### Hava durumu ve hava kalitesi (Open-Meteo)
|
|
88
138
|
|
|
89
139
|
```bash
|
|
90
|
-
#
|
|
140
|
+
# Seçili şehir
|
|
91
141
|
turkiyem hava guncel
|
|
92
142
|
|
|
93
|
-
#
|
|
143
|
+
# Şehir adıyla
|
|
94
144
|
turkiyem hava guncel istanbul
|
|
145
|
+
turkiyem hava saatlik ankara --gun 3
|
|
146
|
+
turkiyem hava kalite izmir
|
|
95
147
|
|
|
96
|
-
#
|
|
148
|
+
# Koordinatla
|
|
97
149
|
turkiyem hava guncel 41.0082,28.9784
|
|
150
|
+
```
|
|
98
151
|
|
|
99
|
-
|
|
100
|
-
turkiyem hava saatlik istanbul
|
|
152
|
+
### Ekonomi (TCMB)
|
|
101
153
|
|
|
102
|
-
|
|
103
|
-
|
|
154
|
+
```bash
|
|
155
|
+
# Sık kullanılan kurlar (USD, EUR vb.)
|
|
156
|
+
turkiyem doviz
|
|
104
157
|
|
|
105
|
-
#
|
|
106
|
-
turkiyem
|
|
158
|
+
# Tüm kurlar listesi
|
|
159
|
+
turkiyem doviz --tum
|
|
107
160
|
```
|
|
108
161
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
162
|
+
## Veri Kaynakları
|
|
163
|
+
|
|
164
|
+
| Kaynak | Kullanım |
|
|
165
|
+
|---|---|
|
|
166
|
+
| AFAD | Deprem verileri |
|
|
167
|
+
| EGO | Ankara hat/sefer saatleri |
|
|
168
|
+
| IETT GTFS | İstanbul hat özeti |
|
|
169
|
+
| IETT SOAP (PlanlananSeferSaati) | İstanbul planlanan sefer saatleri |
|
|
170
|
+
| IETT SOAP (SeferGerceklesme) | İstanbul canlı araç konumu |
|
|
171
|
+
| Open-Meteo Forecast | Güncel hava + saatlik tahmin |
|
|
172
|
+
| Open-Meteo Air Quality | Hava kalitesi |
|
|
173
|
+
| TCMB | Döviz kurları |
|
|
174
|
+
|
|
175
|
+
## Mimari ve Proje Yapısı
|
|
176
|
+
|
|
177
|
+
```text
|
|
178
|
+
src/
|
|
179
|
+
commands/ # CLI komut handler'ları
|
|
180
|
+
services/ # Dış API / scraping / SOAP katmanı
|
|
181
|
+
utils/ # Tablo, cache, config, banner yardımcıları
|
|
182
|
+
index.js # Commander giriş noktası
|
|
115
183
|
```
|
|
116
184
|
|
|
117
|
-
|
|
185
|
+
Mimari prensipleri:
|
|
186
|
+
- Komut katmanı ile servis katmanı ayrımı
|
|
187
|
+
- API bağımlılıklarının servis içinde izole edilmesi
|
|
188
|
+
- Tekrarlanan işlerin util katmanına alınması
|
|
189
|
+
- Tüm dış isteklerde timeout + anlamlı hata mesajı
|
|
118
190
|
|
|
119
|
-
|
|
120
|
-
turkiyem --version
|
|
121
|
-
```
|
|
191
|
+
## Yapılandırma ve Cache
|
|
122
192
|
|
|
123
|
-
|
|
193
|
+
- Seçili şehir dosyası:
|
|
194
|
+
- `~/.turkiyem/config.json`
|
|
195
|
+
- Varsayılan cache:
|
|
196
|
+
- Bellek içi (`node-cache`)
|
|
197
|
+
- Kaynak bazlı TTL (ör. IETT SOAP, hava durumu vb.)
|
|
124
198
|
|
|
125
|
-
|
|
199
|
+
Temizleme:
|
|
126
200
|
|
|
127
|
-
|
|
201
|
+
```bash
|
|
202
|
+
turkiyem temizle
|
|
203
|
+
```
|
|
128
204
|
|
|
129
|
-
|
|
130
|
-
|--------|----------|
|
|
131
|
-
| AFAD | Deprem verileri (deprem.afad.gov.tr) |
|
|
132
|
-
| EGO | Ankara otobus sefer saatleri (ego.gov.tr) |
|
|
133
|
-
| IETT | Istanbul GTFS hat verileri (data.ibb.gov.tr) |
|
|
134
|
-
| IETT SOAP | Planlanan sefer saatleri (api.ibb.gov.tr) |
|
|
135
|
-
| Open-Meteo | Guncel hava ve saatlik tahmin (api.open-meteo.com) |
|
|
136
|
-
| Open-Meteo AQ | Hava kalitesi (air-quality-api.open-meteo.com) |
|
|
205
|
+
## Hata Yönetimi
|
|
137
206
|
|
|
138
|
-
|
|
207
|
+
Projede:
|
|
208
|
+
- `unhandledRejection` ve `uncaughtException` yakalanır
|
|
209
|
+
- API hataları kullanıcı dostu mesajlara çevrilir
|
|
210
|
+
- Ağ timeout / bağlantı sorunları için özel açıklamalar verilir
|
|
211
|
+
- Erişilemeyen kaynaklarda mümkün olan yerlerde fallback uygulanır
|
|
139
212
|
|
|
140
|
-
|
|
213
|
+
## Geliştirme
|
|
141
214
|
|
|
142
215
|
```bash
|
|
143
|
-
npm
|
|
216
|
+
npm install
|
|
217
|
+
npm start
|
|
144
218
|
```
|
|
145
219
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
3. Yayinlayin:
|
|
220
|
+
Örnek geliştirme doğrulama komutları:
|
|
149
221
|
|
|
150
222
|
```bash
|
|
151
|
-
|
|
223
|
+
node src/index.js help
|
|
224
|
+
node src/index.js deprem son24
|
|
225
|
+
node src/index.js hava guncel istanbul
|
|
226
|
+
node src/index.js hat 34AS
|
|
152
227
|
```
|
|
153
228
|
|
|
154
|
-
|
|
229
|
+
## Yayınlama (npm)
|
|
155
230
|
|
|
156
231
|
```bash
|
|
232
|
+
# 1) sürüm artır
|
|
157
233
|
npm version patch
|
|
158
|
-
|
|
234
|
+
|
|
235
|
+
# 2) publish
|
|
236
|
+
npm publish --access public
|
|
237
|
+
|
|
238
|
+
# 3) kontrol
|
|
239
|
+
npm view turkiyem version
|
|
159
240
|
```
|
|
160
241
|
|
|
242
|
+
## Sık Karşılaşılan Sorunlar
|
|
243
|
+
|
|
244
|
+
### `npm publish` 403 (aynı sürüm)
|
|
245
|
+
Önceden yayınlanmış bir sürüm numarasını tekrar gönderemezsiniz.
|
|
246
|
+
Çözüm: `npm version patch|minor|major` sonrası tekrar publish.
|
|
247
|
+
|
|
248
|
+
### `npm publish` 403 (2FA / token)
|
|
249
|
+
NPM hesabınız için 2FA veya granular token gereksinimi olabilir.
|
|
250
|
+
Çözüm: NPM hesabında token/2FA ayarlarını tamamlayın.
|
|
251
|
+
|
|
252
|
+
### IETT canlı konum 500
|
|
253
|
+
SOAP servis tarafı geçici olarak hata döndürebilir.
|
|
254
|
+
Bu durumda kısa süre sonra tekrar deneyin.
|
|
255
|
+
|
|
161
256
|
## Lisans
|
|
162
257
|
|
|
163
258
|
MIT
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "turkiyem",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.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",
|
|
7
7
|
"bin": {
|
|
8
|
-
"turkiyem": "
|
|
8
|
+
"turkiyem": "src/index.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node src/index.js",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { fetchExchangeRates } from '../services/tcmbService.js';
|
|
4
|
+
import { createDovizTable } from '../utils/display.js';
|
|
5
|
+
|
|
6
|
+
export async function dovizKurlari(options) {
|
|
7
|
+
const spinner = ora('TCMB döviz kurları alınıyor...').start();
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const result = await fetchExchangeRates();
|
|
11
|
+
spinner.succeed(`TCMB kurları alındı (Tarih: ${result.date || '-'})`);
|
|
12
|
+
|
|
13
|
+
const popular = ['USD', 'EUR', 'GBP', 'CHF', 'JPY', 'CAD', 'AUD', 'DKK', 'SEK', 'NOK'];
|
|
14
|
+
const kurlar = options.tum ? result.currencies : result.currencies.filter(c => popular.includes(c.kodu));
|
|
15
|
+
|
|
16
|
+
console.log('');
|
|
17
|
+
console.log(createDovizTable({ ...result, currencies: kurlar }));
|
|
18
|
+
|
|
19
|
+
if (!options.tum) {
|
|
20
|
+
console.log('');
|
|
21
|
+
console.log(chalk.gray(' Tüm kurları görmek için: turkiyem doviz --tum'));
|
|
22
|
+
}
|
|
23
|
+
} catch (err) {
|
|
24
|
+
spinner.fail(chalk.red(err.message));
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/commands/hat.js
CHANGED
|
@@ -2,9 +2,11 @@ import chalk from 'chalk';
|
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import { getCity } from '../utils/config.js';
|
|
4
4
|
import { fetchEgoSchedule } from '../services/egoService.js';
|
|
5
|
-
import { fetchIettRouteWithPlannedTimes } from '../services/iettService.js';
|
|
5
|
+
import { fetchIettLiveVehicles, fetchIettRouteWithPlannedTimes } from '../services/iettService.js';
|
|
6
6
|
import {
|
|
7
7
|
createEgoInfoTable,
|
|
8
|
+
createIettLiveDetailTable,
|
|
9
|
+
createIettLiveSummaryTable,
|
|
8
10
|
createEgoScheduleTable,
|
|
9
11
|
createIettPlannedTimesTable,
|
|
10
12
|
createRouteTable,
|
|
@@ -34,6 +36,48 @@ export async function hatSorgula(hatNo) {
|
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
export async function hatCanliSorgula(hatNo, options = {}) {
|
|
40
|
+
if (!hatNo) {
|
|
41
|
+
console.log(chalk.red('Hat numarası belirtmelisiniz. Örnek: turkiyem hat canli 34AS'));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const city = getCity();
|
|
46
|
+
if (!city) {
|
|
47
|
+
console.log(chalk.yellow('Henüz şehir seçmediniz. Önce şehir seçin:'));
|
|
48
|
+
console.log(chalk.cyan(' turkiyem sehir istanbul'));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (city !== 'istanbul') {
|
|
53
|
+
console.log(chalk.yellow('Canlı konum özelliği sadece İstanbul (IETT) için kullanılabilir.'));
|
|
54
|
+
console.log(chalk.gray('Önce şehir seçimini istanbul yapın: turkiyem sehir istanbul'));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const spinner = ora(`IETT canlı araç konumları alınıyor (${hatNo})...`).start();
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const liveData = await fetchIettLiveVehicles(hatNo);
|
|
62
|
+
spinner.succeed(`IETT canlı konum verisi alındı (${liveData.summary.totalVehicles} araç)`);
|
|
63
|
+
console.log('');
|
|
64
|
+
|
|
65
|
+
console.log(chalk.white.bold(' IETT Canlı Konum Özeti'));
|
|
66
|
+
console.log(createIettLiveSummaryTable(liveData));
|
|
67
|
+
|
|
68
|
+
if (options.detay) {
|
|
69
|
+
console.log('');
|
|
70
|
+
console.log(chalk.white.bold(' IETT Canlı Konum Detayları'));
|
|
71
|
+
console.log(createIettLiveDetailTable(liveData));
|
|
72
|
+
} else {
|
|
73
|
+
console.log('');
|
|
74
|
+
console.log(chalk.gray(' Detay tablo için: turkiyem hat canli <numara> --detay'));
|
|
75
|
+
}
|
|
76
|
+
} catch (err) {
|
|
77
|
+
spinner.fail(chalk.red(err.message));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
37
81
|
async function queryEgo(hatNo) {
|
|
38
82
|
const spinner = ora(`EGO hat ${hatNo} bilgileri alınıyor...`).start();
|
|
39
83
|
|
package/src/index.js
CHANGED
|
@@ -5,10 +5,11 @@ import { Command } from 'commander';
|
|
|
5
5
|
import { createRequire } from 'node:module';
|
|
6
6
|
import { printBanner, printHelp } from './utils/banner.js';
|
|
7
7
|
import { sehirSec } from './commands/sehir.js';
|
|
8
|
-
import { hatSorgula } from './commands/hat.js';
|
|
8
|
+
import { hatCanliSorgula, hatSorgula } from './commands/hat.js';
|
|
9
9
|
import { depremSon24, deprem7Gun, depremBuyukluk } from './commands/deprem.js';
|
|
10
10
|
import { havaGuncel, havaKalitesi, havaSaatlik } from './commands/hava.js';
|
|
11
11
|
import { temizle } from './commands/temizle.js';
|
|
12
|
+
import { dovizKurlari } from './commands/doviz.js';
|
|
12
13
|
|
|
13
14
|
const require = createRequire(import.meta.url);
|
|
14
15
|
const pkg = require('../package.json');
|
|
@@ -39,11 +40,20 @@ program
|
|
|
39
40
|
sehirSec(sehir);
|
|
40
41
|
});
|
|
41
42
|
|
|
42
|
-
program
|
|
43
|
-
.command('hat
|
|
44
|
-
.description('Hat bilgilerini sorgula')
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
const hatCmd = program
|
|
44
|
+
.command('hat')
|
|
45
|
+
.description('Hat bilgilerini sorgula');
|
|
46
|
+
|
|
47
|
+
hatCmd
|
|
48
|
+
.argument('[arg1]', 'Hat numarası veya "canli"')
|
|
49
|
+
.argument('[arg2]', 'Canlı komutu için hat numarası')
|
|
50
|
+
.option('--detay', 'Araç bazlı detay tabloyu göster')
|
|
51
|
+
.action(async (arg1, arg2, options) => {
|
|
52
|
+
if (arg1 === 'canli') {
|
|
53
|
+
await hatCanliSorgula(arg2, options);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
await hatSorgula(arg1);
|
|
47
57
|
});
|
|
48
58
|
|
|
49
59
|
const depremCmd = program
|
|
@@ -104,6 +114,14 @@ program
|
|
|
104
114
|
temizle();
|
|
105
115
|
});
|
|
106
116
|
|
|
117
|
+
program
|
|
118
|
+
.command('doviz')
|
|
119
|
+
.description('TCMB güncel döviz kurlarını sorgula')
|
|
120
|
+
.option('--tum', 'Tüm kurları göster')
|
|
121
|
+
.action(async (options) => {
|
|
122
|
+
await dovizKurlari(options);
|
|
123
|
+
});
|
|
124
|
+
|
|
107
125
|
program
|
|
108
126
|
.command('help')
|
|
109
127
|
.description('Yardım göster')
|
|
@@ -5,6 +5,8 @@ const DATASTORE_SQL_URL = 'https://data.ibb.gov.tr/api/3/action/datastore_search
|
|
|
5
5
|
const DATASTORE_SEARCH_URL = 'https://data.ibb.gov.tr/api/3/action/datastore_search';
|
|
6
6
|
const PLANNED_TIME_SOAP_URL = 'https://api.ibb.gov.tr/iett/UlasimAnaVeri/PlanlananSeferSaati.asmx';
|
|
7
7
|
const PLANNED_TIME_SOAP_ACTION = 'http://tempuri.org/GetPlanlananSeferSaati_json';
|
|
8
|
+
const LIVE_SOAP_URL = 'https://api.ibb.gov.tr/iett/FiloDurum/SeferGerceklesme.asmx';
|
|
9
|
+
const LIVE_SOAP_ACTION = 'http://tempuri.org/GetHatOtoKonum_json';
|
|
8
10
|
|
|
9
11
|
const RESOURCE_IDS = {
|
|
10
12
|
routes: '46dbe388-c8c2-45c4-ac72-c06953de56a2',
|
|
@@ -170,6 +172,79 @@ function extractSoapResultJson(xmlText) {
|
|
|
170
172
|
return JSON.parse(jsonText);
|
|
171
173
|
}
|
|
172
174
|
|
|
175
|
+
function buildLiveSoapBody(routeCode) {
|
|
176
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
177
|
+
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
178
|
+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
179
|
+
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
|
180
|
+
<soap:Body>
|
|
181
|
+
<GetHatOtoKonum_json xmlns="http://tempuri.org/">
|
|
182
|
+
<HatNo>${routeCode}</HatNo>
|
|
183
|
+
</GetHatOtoKonum_json>
|
|
184
|
+
</soap:Body>
|
|
185
|
+
</soap:Envelope>`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function extractLiveSoapResultJson(xmlText) {
|
|
189
|
+
const match = xmlText.match(
|
|
190
|
+
/<GetHatOtoKonum_jsonResult>([\s\S]*?)<\/GetHatOtoKonum_jsonResult>/i,
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
if (!match || !match[1]) return [];
|
|
194
|
+
const jsonText = match[1].trim();
|
|
195
|
+
if (!jsonText || jsonText === '[]') return [];
|
|
196
|
+
return JSON.parse(jsonText);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function normalizeLiveVehicles(records) {
|
|
200
|
+
return records.map((item) => ({
|
|
201
|
+
vehicleDoorNo: item.kapino || '-',
|
|
202
|
+
longitude: item.boylam || '-',
|
|
203
|
+
latitude: item.enlem || '-',
|
|
204
|
+
routeCode: item.hatkodu || '-',
|
|
205
|
+
routeVariantCode: item.guzergahkodu || '-',
|
|
206
|
+
routeName: item.hatad || '-',
|
|
207
|
+
direction: item.yon || '-',
|
|
208
|
+
lastLocationTime: item.son_konum_zamani || '-',
|
|
209
|
+
nearestStopCode: item.yakinDurakKodu || '-',
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function parseDateValue(value) {
|
|
214
|
+
if (!value || value === '-') return Number.NaN;
|
|
215
|
+
const normalized = value.includes('T') ? value : value.replace(' ', 'T');
|
|
216
|
+
const ts = new Date(normalized).getTime();
|
|
217
|
+
return Number.isNaN(ts) ? Number.NaN : ts;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function buildLiveSummary(vehicles) {
|
|
221
|
+
const directionCounter = new Map();
|
|
222
|
+
let latestTime = '-';
|
|
223
|
+
let latestTs = Number.NEGATIVE_INFINITY;
|
|
224
|
+
|
|
225
|
+
for (const vehicle of vehicles) {
|
|
226
|
+
const direction = vehicle.direction || '-';
|
|
227
|
+
directionCounter.set(direction, (directionCounter.get(direction) || 0) + 1);
|
|
228
|
+
|
|
229
|
+
const ts = parseDateValue(vehicle.lastLocationTime);
|
|
230
|
+
if (!Number.isNaN(ts) && ts > latestTs) {
|
|
231
|
+
latestTs = ts;
|
|
232
|
+
latestTime = vehicle.lastLocationTime;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const directionDistribution = Array.from(directionCounter.entries()).map(([direction, count]) => ({
|
|
237
|
+
direction,
|
|
238
|
+
count,
|
|
239
|
+
}));
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
totalVehicles: vehicles.length,
|
|
243
|
+
latestLocationTime: latestTime,
|
|
244
|
+
directionDistribution,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
173
248
|
export async function fetchIettPlannedTimes(routeCode) {
|
|
174
249
|
const code = routeCode.toUpperCase();
|
|
175
250
|
const cacheKey = `iett_soap_times_${code}`;
|
|
@@ -317,3 +392,64 @@ export async function fetchIettRouteWithPlannedTimes(routeCode) {
|
|
|
317
392
|
};
|
|
318
393
|
}
|
|
319
394
|
}
|
|
395
|
+
|
|
396
|
+
export async function fetchIettLiveVehicles(routeCode) {
|
|
397
|
+
const code = routeCode.toUpperCase();
|
|
398
|
+
const cacheKey = `iett_live_${code}`;
|
|
399
|
+
const cached = getCached(cacheKey);
|
|
400
|
+
if (cached) return cached;
|
|
401
|
+
|
|
402
|
+
const body = buildLiveSoapBody(code);
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
const response = await axios.post(LIVE_SOAP_URL, body, {
|
|
406
|
+
timeout: 15000,
|
|
407
|
+
headers: {
|
|
408
|
+
'Content-Type': 'text/xml; charset=utf-8',
|
|
409
|
+
SOAPAction: LIVE_SOAP_ACTION,
|
|
410
|
+
...API_HEADERS,
|
|
411
|
+
},
|
|
412
|
+
responseType: 'text',
|
|
413
|
+
maxRedirects: 5,
|
|
414
|
+
validateStatus: (status) => status >= 200 && status < 600,
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
if (response.status === 403) {
|
|
418
|
+
throw new Error('IETT canlı konum servisi erişimi engellendi (403).');
|
|
419
|
+
}
|
|
420
|
+
if (response.status >= 500) {
|
|
421
|
+
throw new Error(`IETT canlı konum servisi şu anda erişilemiyor (HTTP ${response.status}).`);
|
|
422
|
+
}
|
|
423
|
+
if (response.status !== 200) {
|
|
424
|
+
throw new Error(`IETT canlı konum servisi HTTP ${response.status} döndürdü.`);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const parsed = extractLiveSoapResultJson(response.data);
|
|
428
|
+
const vehicles = normalizeLiveVehicles(parsed);
|
|
429
|
+
|
|
430
|
+
if (vehicles.length === 0) {
|
|
431
|
+
throw new Error(`"${routeCode}" hattı için aktif araç konumu bulunamadı.`);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const summary = buildLiveSummary(vehicles);
|
|
435
|
+
const result = {
|
|
436
|
+
routeCode: code,
|
|
437
|
+
summary,
|
|
438
|
+
vehicles,
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
setCached(cacheKey, result, CACHE_TTL.IETT_LIVE);
|
|
442
|
+
return result;
|
|
443
|
+
} catch (err) {
|
|
444
|
+
if (err.code === 'ECONNABORTED') {
|
|
445
|
+
throw new Error('IETT canlı konum servisi zaman aşımına uğradı.');
|
|
446
|
+
}
|
|
447
|
+
if (err.code === 'ENOTFOUND' || err.code === 'ECONNREFUSED') {
|
|
448
|
+
throw new Error('IETT canlı konum servisine bağlanılamadı.');
|
|
449
|
+
}
|
|
450
|
+
if (err instanceof SyntaxError) {
|
|
451
|
+
throw new Error('IETT canlı konum yanıtı çözümlenemedi.');
|
|
452
|
+
}
|
|
453
|
+
throw err;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import * as cheerio from 'cheerio';
|
|
3
|
+
import { getCached, setCached } from '../utils/cache.js';
|
|
4
|
+
|
|
5
|
+
const CACHE_KEY = 'tcmb_kur';
|
|
6
|
+
const CACHE_TTL = 3600; // 1 saat
|
|
7
|
+
const URL = 'https://www.tcmb.gov.tr/kurlar/today.xml';
|
|
8
|
+
|
|
9
|
+
export async function fetchExchangeRates() {
|
|
10
|
+
const cached = getCached(CACHE_KEY);
|
|
11
|
+
if (cached) return cached;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const response = await axios.get(URL, { timeout: 10000 });
|
|
15
|
+
const $ = cheerio.load(response.data, { xmlMode: true });
|
|
16
|
+
|
|
17
|
+
const date = $('Tarih_Date').attr('Tarih');
|
|
18
|
+
const bultenNo = $('Tarih_Date').attr('Bulten_No');
|
|
19
|
+
|
|
20
|
+
const currencies = [];
|
|
21
|
+
|
|
22
|
+
$('Currency').each((_, el) => {
|
|
23
|
+
const isim = $(el).find('Isim').text().trim();
|
|
24
|
+
const code = $(el).attr('CurrencyCode');
|
|
25
|
+
const alis = $(el).find('ForexBuying').text().trim();
|
|
26
|
+
const satis = $(el).find('ForexSelling').text().trim();
|
|
27
|
+
|
|
28
|
+
if (isim && code) {
|
|
29
|
+
currencies.push({
|
|
30
|
+
isim,
|
|
31
|
+
kodu: code,
|
|
32
|
+
alis,
|
|
33
|
+
satis
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const result = { date, bultenNo, currencies };
|
|
39
|
+
setCached(CACHE_KEY, result, CACHE_TTL);
|
|
40
|
+
return result;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) {
|
|
43
|
+
throw new Error('TCMB servisine erişilemedi (Zaman aşımı).');
|
|
44
|
+
}
|
|
45
|
+
throw new Error(`TCMB veri hatası: ${error.message}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/utils/banner.js
CHANGED
|
@@ -22,12 +22,14 @@ export function printHelp() {
|
|
|
22
22
|
console.log(chalk.white.bold(' Komutlar:\n'));
|
|
23
23
|
console.log(chalk.cyan(' turkiyem sehir <ankara|istanbul>') + chalk.gray(' Şehir seç'));
|
|
24
24
|
console.log(chalk.cyan(' turkiyem hat <numara>') + chalk.gray(' Hat sorgula'));
|
|
25
|
+
console.log(chalk.cyan(' turkiyem hat canli <numara> [--detay]') + chalk.gray(' IETT canlı konum'));
|
|
25
26
|
console.log(chalk.cyan(' turkiyem hava guncel [sehir|lat,lon]') + chalk.gray(' Güncel hava'));
|
|
26
27
|
console.log(chalk.cyan(' turkiyem hava saatlik [sehir|lat,lon] -g 2') + chalk.gray(' Saatlik tahmin'));
|
|
27
28
|
console.log(chalk.cyan(' turkiyem hava kalite [sehir|lat,lon]') + chalk.gray(' Hava kalitesi'));
|
|
28
29
|
console.log(chalk.cyan(' turkiyem deprem son24') + chalk.gray(' Son 24 saat depremler'));
|
|
29
30
|
console.log(chalk.cyan(' turkiyem deprem 7gun') + chalk.gray(' Son 7 gün depremler'));
|
|
30
31
|
console.log(chalk.cyan(' turkiyem deprem buyukluk <deger>') + chalk.gray(' Büyüklüğe göre filtrele'));
|
|
32
|
+
console.log(chalk.cyan(' turkiyem doviz [--tum]') + chalk.gray(' TCMB Döviz kurları'));
|
|
31
33
|
console.log(chalk.cyan(' turkiyem temizle') + chalk.gray(' Cache ve config temizle'));
|
|
32
34
|
console.log(chalk.cyan(' turkiyem --version') + chalk.gray(' Versiyon göster'));
|
|
33
35
|
console.log('');
|
package/src/utils/cache.js
CHANGED
package/src/utils/display.js
CHANGED
|
@@ -198,3 +198,77 @@ export function createAirQualityTable(result) {
|
|
|
198
198
|
|
|
199
199
|
return table.toString();
|
|
200
200
|
}
|
|
201
|
+
|
|
202
|
+
export function createIettLiveSummaryTable(liveData) {
|
|
203
|
+
const table = new Table({
|
|
204
|
+
style: { head: [], border: ['gray'] },
|
|
205
|
+
wordWrap: true,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const directionText = (liveData.summary?.directionDistribution || [])
|
|
209
|
+
.map((item) => `${item.direction}: ${item.count}`)
|
|
210
|
+
.join(' | ');
|
|
211
|
+
|
|
212
|
+
table.push(
|
|
213
|
+
{ [chalk.cyan('Hat Kodu')]: liveData.routeCode || '-' },
|
|
214
|
+
{ [chalk.cyan('Aktif Araç Sayısı')]: String(liveData.summary?.totalVehicles ?? 0) },
|
|
215
|
+
{ [chalk.cyan('Yön Dağılımı')]: directionText || '-' },
|
|
216
|
+
{ [chalk.cyan('Son Konum Zamanı')]: liveData.summary?.latestLocationTime || '-' },
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
return table.toString();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function createIettLiveDetailTable(liveData) {
|
|
223
|
+
const table = new Table({
|
|
224
|
+
head: [
|
|
225
|
+
chalk.white.bold('Kapı No'),
|
|
226
|
+
chalk.white.bold('Yön'),
|
|
227
|
+
chalk.white.bold('Enlem'),
|
|
228
|
+
chalk.white.bold('Boylam'),
|
|
229
|
+
chalk.white.bold('Yakın Durak'),
|
|
230
|
+
chalk.white.bold('Son Konum Zamanı'),
|
|
231
|
+
],
|
|
232
|
+
colWidths: [10, 24, 12, 12, 14, 22],
|
|
233
|
+
style: { head: [], border: ['gray'] },
|
|
234
|
+
wordWrap: true,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
for (const vehicle of liveData.vehicles || []) {
|
|
238
|
+
table.push([
|
|
239
|
+
vehicle.vehicleDoorNo || '-',
|
|
240
|
+
vehicle.direction || '-',
|
|
241
|
+
String(vehicle.latitude ?? '-'),
|
|
242
|
+
String(vehicle.longitude ?? '-'),
|
|
243
|
+
vehicle.nearestStopCode || '-',
|
|
244
|
+
vehicle.lastLocationTime || '-',
|
|
245
|
+
]);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return table.toString();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function createDovizTable(result) {
|
|
252
|
+
const table = new Table({
|
|
253
|
+
head: [
|
|
254
|
+
chalk.white.bold('Kod'),
|
|
255
|
+
chalk.white.bold('Döviz Cinsi'),
|
|
256
|
+
chalk.white.bold('Alış (TL)'),
|
|
257
|
+
chalk.white.bold('Satış (TL)'),
|
|
258
|
+
],
|
|
259
|
+
colWidths: [8, 30, 15, 15],
|
|
260
|
+
style: { head: [], border: ['gray'] },
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
for (const c of result.currencies) {
|
|
264
|
+
if (!c.alis && !c.satis) continue;
|
|
265
|
+
table.push([
|
|
266
|
+
chalk.cyan(c.kodu),
|
|
267
|
+
c.isim,
|
|
268
|
+
c.alis || '-',
|
|
269
|
+
c.satis || '-'
|
|
270
|
+
]);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return table.toString();
|
|
274
|
+
}
|