tdk-api-wrapper 1.0.1 → 1.2.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 +53 -6
- package/dist/chunk-2TA5PMVZ.mjs +741 -0
- package/dist/cli.js +683 -85
- package/dist/cli.mjs +222 -37
- package/dist/index.d.mts +173 -5
- package/dist/index.d.ts +173 -5
- package/dist/index.js +471 -52
- package/dist/index.mjs +9 -3
- package/package.json +1 -1
- package/src/cli.ts +227 -36
- package/src/errors.ts +38 -0
- package/src/index.ts +1 -0
- package/src/tdk.ts +449 -61
- package/src/types.ts +36 -0
- package/dist/chunk-MGSXCUAX.mjs +0 -325
package/dist/cli.js
CHANGED
|
@@ -23,12 +23,41 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
mod
|
|
24
24
|
));
|
|
25
25
|
|
|
26
|
+
// src/errors.ts
|
|
27
|
+
var TDKError = class extends Error {
|
|
28
|
+
constructor(message) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = "TDKError";
|
|
31
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var TDKValidationError = class extends TDKError {
|
|
35
|
+
constructor(message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "TDKValidationError";
|
|
38
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var TDKNetworkError = class extends TDKError {
|
|
42
|
+
status;
|
|
43
|
+
cause;
|
|
44
|
+
constructor(message, options) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = "TDKNetworkError";
|
|
47
|
+
this.status = options?.status;
|
|
48
|
+
this.cause = options?.cause;
|
|
49
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
26
53
|
// src/tdk.ts
|
|
27
54
|
var fs = __toESM(require("fs"));
|
|
28
55
|
var path = __toESM(require("path"));
|
|
29
56
|
var os = __toESM(require("os"));
|
|
57
|
+
var https = __toESM(require("https"));
|
|
30
58
|
var TDK = class {
|
|
31
59
|
static BASE_URL = "https://sozluk.gov.tr";
|
|
60
|
+
static AUDIO_API_HOST = "api.sozluk.gov.tr";
|
|
32
61
|
// Cache Mechanism
|
|
33
62
|
static isCacheEnabled = false;
|
|
34
63
|
static wordCache = /* @__PURE__ */ new Map();
|
|
@@ -59,35 +88,42 @@ var TDK = class {
|
|
|
59
88
|
*/
|
|
60
89
|
static async getWord(word2) {
|
|
61
90
|
if (!word2 || word2.trim() === "") {
|
|
62
|
-
throw new
|
|
91
|
+
throw new TDKValidationError("Word parameter cannot be empty.");
|
|
63
92
|
}
|
|
64
|
-
const cleanWord = word2.trim().
|
|
93
|
+
const cleanWord = word2.trim().toLocaleLowerCase("tr-TR");
|
|
65
94
|
if (this.isCacheEnabled && this.wordCache.has(cleanWord)) {
|
|
66
95
|
return this.wordCache.get(cleanWord);
|
|
67
96
|
}
|
|
68
97
|
const url = `${this.BASE_URL}/gts?ara=${encodeURIComponent(cleanWord)}`;
|
|
98
|
+
let response;
|
|
69
99
|
try {
|
|
70
|
-
|
|
100
|
+
response = await fetch(url, {
|
|
71
101
|
headers: { "User-Agent": "TDK-API-Nodejs-Wrapper/1.0" }
|
|
72
102
|
});
|
|
73
|
-
if (!response.ok)
|
|
74
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
75
|
-
const data = await response.json();
|
|
76
|
-
if (!Array.isArray(data) && data && "error" in data) {
|
|
77
|
-
if (this.isCacheEnabled)
|
|
78
|
-
this.wordCache.set(cleanWord, []);
|
|
79
|
-
return [];
|
|
80
|
-
}
|
|
81
|
-
const results = data;
|
|
82
|
-
if (this.isCacheEnabled) {
|
|
83
|
-
this.wordCache.set(cleanWord, results);
|
|
84
|
-
}
|
|
85
|
-
return results;
|
|
86
103
|
} catch (error) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
104
|
+
throw new TDKNetworkError("Failed to fetch word from TDK: request failed.", { cause: error });
|
|
105
|
+
}
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
throw new TDKNetworkError(`Failed to fetch word from TDK: HTTP ${response.status}.`, {
|
|
108
|
+
status: response.status
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
let data;
|
|
112
|
+
try {
|
|
113
|
+
data = await response.json();
|
|
114
|
+
} catch (error) {
|
|
115
|
+
throw new TDKNetworkError("Failed to fetch word from TDK: invalid JSON response.", { cause: error });
|
|
116
|
+
}
|
|
117
|
+
if (!Array.isArray(data) && data && "error" in data) {
|
|
118
|
+
if (this.isCacheEnabled)
|
|
119
|
+
this.wordCache.set(cleanWord, []);
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
const results = data;
|
|
123
|
+
if (this.isCacheEnabled) {
|
|
124
|
+
this.wordCache.set(cleanWord, results);
|
|
90
125
|
}
|
|
126
|
+
return results;
|
|
91
127
|
}
|
|
92
128
|
/**
|
|
93
129
|
* Helper method to get only the meanings (definitions) of a word as a string array.
|
|
@@ -108,24 +144,61 @@ var TDK = class {
|
|
|
108
144
|
return meanings;
|
|
109
145
|
}
|
|
110
146
|
/**
|
|
111
|
-
*
|
|
147
|
+
* `sozluk.gov.tr`'s dedicated `/autocomplete.json` (and `/data/autocomplete.json`)
|
|
148
|
+
* routes no longer serve JSON — they fall through to the SPA's `index.html`.
|
|
149
|
+
* The full ~81k-word headword list the site's own autocomplete UI uses is
|
|
150
|
+
* instead bundled directly into its main JS asset as a
|
|
151
|
+
* `JSON.parse(\`[{"madde":"..."}]\`)` literal, so this fetches the home
|
|
152
|
+
* page to find that asset's current hashed filename, downloads it (a few
|
|
153
|
+
* MB, only once per process), and extracts the literal out of it. Fragile
|
|
154
|
+
* scraping of an implementation detail — if TDK's build stops embedding
|
|
155
|
+
* this, this fails closed to `[]` rather than throwing.
|
|
156
|
+
*/
|
|
157
|
+
static async fetchAutocompleteData() {
|
|
158
|
+
try {
|
|
159
|
+
const homeResponse = await fetch(`${this.BASE_URL}/`, {
|
|
160
|
+
headers: { "User-Agent": "TDK-API-Nodejs-Wrapper/1.0" }
|
|
161
|
+
});
|
|
162
|
+
if (!homeResponse.ok)
|
|
163
|
+
return [];
|
|
164
|
+
const html = await homeResponse.text();
|
|
165
|
+
const scriptMatch = html.match(/src="(\/assets\/index-[^"]+\.js)"/);
|
|
166
|
+
if (!scriptMatch)
|
|
167
|
+
return [];
|
|
168
|
+
const bundleResponse = await fetch(`${this.BASE_URL}${scriptMatch[1]}`, {
|
|
169
|
+
headers: { "User-Agent": "TDK-API-Nodejs-Wrapper/1.0" }
|
|
170
|
+
});
|
|
171
|
+
if (!bundleResponse.ok)
|
|
172
|
+
return [];
|
|
173
|
+
const bundleJs = await bundleResponse.text();
|
|
174
|
+
const startMarker = 'JSON.parse(`[{"madde":';
|
|
175
|
+
const startIdx = bundleJs.indexOf(startMarker);
|
|
176
|
+
if (startIdx === -1)
|
|
177
|
+
return [];
|
|
178
|
+
const jsonStart = startIdx + "JSON.parse(".length + 1;
|
|
179
|
+
const jsonEnd = bundleJs.indexOf("`)", jsonStart);
|
|
180
|
+
if (jsonEnd === -1)
|
|
181
|
+
return [];
|
|
182
|
+
const data = JSON.parse(bundleJs.slice(jsonStart, jsonEnd));
|
|
183
|
+
return data.map((item) => item.madde).filter(Boolean);
|
|
184
|
+
} catch {
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Returns autocomplete suggestions for a given prefix, searched over TDK's
|
|
190
|
+
* full headword list (see `fetchAutocompleteData`). The list is fetched
|
|
191
|
+
* and cached once per process regardless of `enableCache()` — the same
|
|
192
|
+
* caching behavior as before — and only cleared by `clearCache()`.
|
|
112
193
|
*/
|
|
113
194
|
static async getSuggestions(prefix) {
|
|
195
|
+
if (!prefix || prefix.trim() === "")
|
|
196
|
+
return [];
|
|
114
197
|
if (this.autocompleteCache.length === 0) {
|
|
115
|
-
|
|
116
|
-
const response = await fetch(`${this.BASE_URL}/autocomplete.json`, {
|
|
117
|
-
headers: { "User-Agent": "TDK-API-Nodejs-Wrapper/1.0" }
|
|
118
|
-
});
|
|
119
|
-
if (response.ok) {
|
|
120
|
-
const data = await response.json();
|
|
121
|
-
this.autocompleteCache = data.map((item) => item.madde);
|
|
122
|
-
}
|
|
123
|
-
} catch (e) {
|
|
124
|
-
return [];
|
|
125
|
-
}
|
|
198
|
+
this.autocompleteCache = await this.fetchAutocompleteData();
|
|
126
199
|
}
|
|
127
|
-
const cleanPrefix = prefix.
|
|
128
|
-
return this.autocompleteCache.filter((w) => w.
|
|
200
|
+
const cleanPrefix = prefix.trim().toLocaleLowerCase("tr-TR");
|
|
201
|
+
return this.autocompleteCache.filter((w) => w.toLocaleLowerCase("tr-TR").startsWith(cleanPrefix)).slice(0, 10);
|
|
129
202
|
}
|
|
130
203
|
/**
|
|
131
204
|
* Returns a list of proverbs and idioms containing the word.
|
|
@@ -146,14 +219,40 @@ var TDK = class {
|
|
|
146
219
|
return proverbs;
|
|
147
220
|
}
|
|
148
221
|
/**
|
|
149
|
-
* Returns the etymological origin of the word
|
|
222
|
+
* Returns the etymological origin of the word, or "Türkçe" if TDK doesn't
|
|
223
|
+
* record a foreign origin for it. Returns `null` only when the word itself
|
|
224
|
+
* isn't found in the dictionary at all.
|
|
150
225
|
*/
|
|
151
226
|
static async getOrigin(word2) {
|
|
152
227
|
const results = await this.getWord(word2);
|
|
153
|
-
if (results.length
|
|
154
|
-
return
|
|
228
|
+
if (results.length === 0)
|
|
229
|
+
return null;
|
|
230
|
+
return results[0].lisan || "T\xFCrk\xE7e";
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Returns whether the word has a recorded foreign etymological origin.
|
|
234
|
+
* Returns `null` (instead of a boolean) when the word isn't found at all.
|
|
235
|
+
*/
|
|
236
|
+
static async isForeignWord(word2) {
|
|
237
|
+
const origin = await this.getOrigin(word2);
|
|
238
|
+
if (origin === null)
|
|
239
|
+
return null;
|
|
240
|
+
return origin !== "T\xFCrk\xE7e";
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Groups a list of words by their etymological origin. Words not found in
|
|
244
|
+
* the dictionary are grouped under "Bilinmiyor". Throttled like getWordsBatch.
|
|
245
|
+
*/
|
|
246
|
+
static async groupByOrigin(words) {
|
|
247
|
+
const groups = {};
|
|
248
|
+
for (const word2 of words) {
|
|
249
|
+
const origin = await this.getOrigin(word2) ?? "Bilinmiyor";
|
|
250
|
+
if (!groups[origin])
|
|
251
|
+
groups[origin] = [];
|
|
252
|
+
groups[origin].push(word2);
|
|
253
|
+
await this.delay(200);
|
|
155
254
|
}
|
|
156
|
-
return
|
|
255
|
+
return groups;
|
|
157
256
|
}
|
|
158
257
|
/**
|
|
159
258
|
* Returns literature examples containing the word.
|
|
@@ -176,15 +275,108 @@ var TDK = class {
|
|
|
176
275
|
return examples;
|
|
177
276
|
}
|
|
178
277
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
278
|
+
* Calls the `api.sozluk.gov.tr/gts-yeni` endpoint the official web UI uses
|
|
279
|
+
* internally (richer than the public `/gts`: includes `seskod`,
|
|
280
|
+
* `anlamEsAnlam`/`anlamKarsitAnlam`, etc). That endpoint 403s unless the
|
|
281
|
+
* request looks like it came from a browser tab on sozluk.gov.tr: it needs
|
|
282
|
+
* an `Origin`/`Referer` pair matching that site AND a browser-like
|
|
283
|
+
* `User-Agent` (our usual `TDK-API-Nodejs-Wrapper/…` UA gets rejected).
|
|
284
|
+
* `fetch` (undici) also strips a manually-set `Origin` header as a
|
|
285
|
+
* forbidden header name, so this uses `node:https` directly instead.
|
|
286
|
+
* This is inherently fragile scraping of an undocumented endpoint — if
|
|
287
|
+
* TDK tightens this check further, this should fail closed to `null`
|
|
288
|
+
* rather than throw.
|
|
289
|
+
*/
|
|
290
|
+
static fetchGtsYeni(word2) {
|
|
291
|
+
return new Promise((resolve) => {
|
|
292
|
+
const req = https.request(
|
|
293
|
+
{
|
|
294
|
+
hostname: this.AUDIO_API_HOST,
|
|
295
|
+
path: `/gts-yeni?ara=${encodeURIComponent(word2)}`,
|
|
296
|
+
method: "GET",
|
|
297
|
+
headers: {
|
|
298
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
|
|
299
|
+
Origin: this.BASE_URL,
|
|
300
|
+
Referer: `${this.BASE_URL}/`
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
(res) => {
|
|
304
|
+
let body = "";
|
|
305
|
+
res.on("data", (chunk) => body += chunk);
|
|
306
|
+
res.on("end", () => {
|
|
307
|
+
try {
|
|
308
|
+
const data = JSON.parse(body);
|
|
309
|
+
resolve(Array.isArray(data) ? data : null);
|
|
310
|
+
} catch {
|
|
311
|
+
resolve(null);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
);
|
|
316
|
+
req.on("error", () => resolve(null));
|
|
317
|
+
req.end();
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
static async fetchSeskod(word2) {
|
|
321
|
+
const data = await this.fetchGtsYeni(word2);
|
|
322
|
+
const seskod = data?.[0]?.seskod;
|
|
323
|
+
return seskod ? String(seskod) : null;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Returns synonyms ("eş anlamlı kelimeler") recorded for the word, pooled
|
|
327
|
+
* across all of its meanings. Uses the same undocumented `gts-yeni`
|
|
328
|
+
* endpoint as `getAudioUrl` — returns `[]` if the lookup fails.
|
|
329
|
+
*/
|
|
330
|
+
static async getSynonyms(word2) {
|
|
331
|
+
if (!word2 || word2.trim() === "")
|
|
332
|
+
return [];
|
|
333
|
+
const data = await this.fetchGtsYeni(word2.trim().toLocaleLowerCase("tr-TR"));
|
|
334
|
+
if (!data)
|
|
335
|
+
return [];
|
|
336
|
+
const synonyms = [];
|
|
337
|
+
for (const entry of data) {
|
|
338
|
+
for (const anlam of entry.anlamlarListe ?? []) {
|
|
339
|
+
for (const es of anlam.anlamEsAnlam ?? []) {
|
|
340
|
+
if (es.deger)
|
|
341
|
+
synonyms.push(es.deger);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return [...new Set(synonyms)];
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Returns antonyms ("zıt anlamlı kelimeler") recorded for the word, pooled
|
|
349
|
+
* across all of its meanings. Uses the same undocumented `gts-yeni`
|
|
350
|
+
* endpoint as `getAudioUrl` — returns `[]` if the lookup fails.
|
|
351
|
+
*/
|
|
352
|
+
static async getAntonyms(word2) {
|
|
353
|
+
if (!word2 || word2.trim() === "")
|
|
354
|
+
return [];
|
|
355
|
+
const data = await this.fetchGtsYeni(word2.trim().toLocaleLowerCase("tr-TR"));
|
|
356
|
+
if (!data)
|
|
357
|
+
return [];
|
|
358
|
+
const antonyms = [];
|
|
359
|
+
for (const entry of data) {
|
|
360
|
+
for (const anlam of entry.anlamlarListe ?? []) {
|
|
361
|
+
for (const ka of anlam.anlamKarsitAnlam ?? []) {
|
|
362
|
+
if (ka.deger)
|
|
363
|
+
antonyms.push(ka.deger);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return [...new Set(antonyms)];
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Returns the direct URL of the audio pronunciation, if TDK has one recorded for this word.
|
|
181
371
|
*/
|
|
182
372
|
static async getAudioUrl(word2) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return `https://sozluk.gov.tr/ses/${encodeURIComponent(word2)}.wav`;
|
|
373
|
+
if (!word2 || word2.trim() === "") {
|
|
374
|
+
throw new TDKValidationError("Word parameter cannot be empty.");
|
|
186
375
|
}
|
|
187
|
-
|
|
376
|
+
const seskod = await this.fetchSeskod(word2.trim().toLocaleLowerCase("tr-TR"));
|
|
377
|
+
if (!seskod)
|
|
378
|
+
return null;
|
|
379
|
+
return `https://${this.AUDIO_API_HOST}/ses/${encodeURIComponent(seskod)}.wav`;
|
|
188
380
|
}
|
|
189
381
|
/**
|
|
190
382
|
* Downloads the audio pronunciation to the specified path.
|
|
@@ -215,14 +407,29 @@ var TDK = class {
|
|
|
215
407
|
}
|
|
216
408
|
const daily = await this.getDailyContent();
|
|
217
409
|
if (daily) {
|
|
218
|
-
const syydMatch = daily.syyd.find((s) => s.yanliskelime.
|
|
410
|
+
const syydMatch = daily.syyd.find((s) => s.yanliskelime.toLocaleLowerCase("tr-TR") === word2.toLocaleLowerCase("tr-TR"));
|
|
219
411
|
if (syydMatch) {
|
|
220
412
|
return { isCorrect: false, word: word2, suggestion: syydMatch.dogrukelime };
|
|
221
413
|
}
|
|
222
|
-
const mixMatch = daily.karistirma.find((s) => s.yanlis.
|
|
414
|
+
const mixMatch = daily.karistirma.find((s) => s.yanlis.toLocaleLowerCase("tr-TR") === word2.toLocaleLowerCase("tr-TR"));
|
|
223
415
|
if (mixMatch) {
|
|
224
416
|
return { isCorrect: false, word: word2, suggestion: mixMatch.dogru };
|
|
225
417
|
}
|
|
418
|
+
const candidates = [
|
|
419
|
+
...daily.syyd.map((s) => s.dogrukelime),
|
|
420
|
+
...daily.karistirma.flatMap((s) => [s.yanlis, s.dogru]),
|
|
421
|
+
...daily.kelime.map((k) => k.madde)
|
|
422
|
+
];
|
|
423
|
+
let best = null;
|
|
424
|
+
for (const candidate of candidates) {
|
|
425
|
+
const distance = this.levenshtein(word2.toLocaleLowerCase("tr-TR"), candidate.toLocaleLowerCase("tr-TR"));
|
|
426
|
+
if (distance > 0 && (!best || distance < best.distance)) {
|
|
427
|
+
best = { candidate, distance };
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (best && best.distance <= 2) {
|
|
431
|
+
return { isCorrect: false, word: word2, suggestion: best.candidate };
|
|
432
|
+
}
|
|
226
433
|
}
|
|
227
434
|
return { isCorrect: false, word: word2 };
|
|
228
435
|
}
|
|
@@ -247,6 +454,94 @@ var TDK = class {
|
|
|
247
454
|
}
|
|
248
455
|
return null;
|
|
249
456
|
}
|
|
457
|
+
/**
|
|
458
|
+
* Returns today's word of the day along with all of its listed meanings.
|
|
459
|
+
*/
|
|
460
|
+
static async getWordOfTheDay() {
|
|
461
|
+
const daily = await this.getDailyContent();
|
|
462
|
+
if (!daily || daily.kelime.length === 0)
|
|
463
|
+
return null;
|
|
464
|
+
const word2 = daily.kelime[0].madde;
|
|
465
|
+
const meanings = daily.kelime.filter((k) => k.madde === word2).map((k) => k.anlam);
|
|
466
|
+
return { word: word2, meanings };
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Picks a random entry (word or proverb) from today's daily content.
|
|
470
|
+
* Note: this samples from today's `getDailyContent()` picks, not the full dictionary.
|
|
471
|
+
*/
|
|
472
|
+
static async getRandomWord() {
|
|
473
|
+
const daily = await this.getDailyContent();
|
|
474
|
+
if (!daily)
|
|
475
|
+
return null;
|
|
476
|
+
const pool = [
|
|
477
|
+
...daily.kelime.map((k) => ({ type: "kelime", madde: k.madde, anlam: k.anlam })),
|
|
478
|
+
...daily.atasoz.map((a) => ({ type: "atasoz", madde: a.madde, anlam: a.anlam }))
|
|
479
|
+
];
|
|
480
|
+
if (pool.length === 0)
|
|
481
|
+
return null;
|
|
482
|
+
return pool[Math.floor(Math.random() * pool.length)];
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Returns the spelling-rule page(s) ("yazım kuralları") linked from TDK's
|
|
486
|
+
* `/icerik` daily-content feed, e.g. `{ adi: "Kısaltmalar", url: "https://..." }`.
|
|
487
|
+
* Note: like `getRandomWord()`, this is NOT a fixed catalog — `/icerik`
|
|
488
|
+
* appears to hand back a single randomly-rotated rule per request, so two
|
|
489
|
+
* calls a second apart can return entirely different rules.
|
|
490
|
+
*/
|
|
491
|
+
static async getKurallar() {
|
|
492
|
+
const daily = await this.getDailyContent();
|
|
493
|
+
return daily?.kural ?? [];
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Fetches the full plain-text content of a named spelling rule (matched
|
|
497
|
+
* case-insensitively, substring match) from `tdk.gov.tr`. Since `/icerik`
|
|
498
|
+
* hands back a single randomly-rotated rule per request (out of a pool of
|
|
499
|
+
* roughly twenty) rather than a fixed catalog, a single `getKurallar()`
|
|
500
|
+
* draw would rarely match a given name — this re-draws (bounded, with a
|
|
501
|
+
* short delay) until it finds a match or gives up. Returns `null` if no
|
|
502
|
+
* match turns up within the attempt budget or the matched page can't be
|
|
503
|
+
* parsed.
|
|
504
|
+
*/
|
|
505
|
+
static async getRule(name) {
|
|
506
|
+
if (!name || name.trim() === "")
|
|
507
|
+
return null;
|
|
508
|
+
const target = name.trim().toLocaleLowerCase("tr-TR");
|
|
509
|
+
for (let attempt = 0; attempt < 25; attempt++) {
|
|
510
|
+
const rules = await this.getKurallar();
|
|
511
|
+
const match = rules.find((r) => r.adi.toLocaleLowerCase("tr-TR").includes(target));
|
|
512
|
+
if (match)
|
|
513
|
+
return this.fetchRuleText(match.url);
|
|
514
|
+
await this.delay(100);
|
|
515
|
+
}
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* `tdk.gov.tr` rule pages are WordPress/Avada-themed. The actual article
|
|
520
|
+
* text lives in `<div ... itemprop="text">...</div>` right before a
|
|
521
|
+
* `<footer class="entry...">` (share buttons, author box, structured-data
|
|
522
|
+
* spans) — cutting there avoids that trailing cruft.
|
|
523
|
+
*/
|
|
524
|
+
static async fetchRuleText(url) {
|
|
525
|
+
try {
|
|
526
|
+
const response = await fetch(url, { headers: { "User-Agent": "TDK-API-Nodejs-Wrapper/1.0" } });
|
|
527
|
+
if (!response.ok)
|
|
528
|
+
return null;
|
|
529
|
+
const html = await response.text();
|
|
530
|
+
const marker = html.indexOf('itemprop="text"');
|
|
531
|
+
if (marker === -1)
|
|
532
|
+
return null;
|
|
533
|
+
const contentStart = html.indexOf(">", marker) + 1;
|
|
534
|
+
const contentEnd = html.indexOf("<footer", contentStart);
|
|
535
|
+
if (contentEnd === -1)
|
|
536
|
+
return null;
|
|
537
|
+
return this.htmlToPlainText(html.slice(contentStart, contentEnd));
|
|
538
|
+
} catch {
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
static htmlToPlainText(html) {
|
|
543
|
+
return html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div)>/gi, "\n\n").replace(/<[^>]+>/g, "").replace(/ /gi, " ").replace(/&/gi, "&").replace(/"/gi, '"').replace(/'|’/gi, "'").replace(/[ \t]+/g, " ").replace(/[ \t]*\n[ \t]*/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
544
|
+
}
|
|
250
545
|
/**
|
|
251
546
|
* Returns compound words that contain this word.
|
|
252
547
|
*/
|
|
@@ -265,6 +560,9 @@ var TDK = class {
|
|
|
265
560
|
}
|
|
266
561
|
/**
|
|
267
562
|
* Returns the part of speech (isim, sıfat, zarf vb.).
|
|
563
|
+
* TDK's `ozelliklerListe` mixes grammatical categories (`tur: "3"`, e.g.
|
|
564
|
+
* sıfat/zarf/isim) with usage-register tags (`tur: "4"`, e.g. mecaz/argo)
|
|
565
|
+
* in the same list — only `tur === "3"` entries are actual parts of speech.
|
|
268
566
|
*/
|
|
269
567
|
static async getPartOfSpeech(word2) {
|
|
270
568
|
const results = await this.getWord(word2);
|
|
@@ -274,7 +572,8 @@ var TDK = class {
|
|
|
274
572
|
for (const anlam of result.anlamlarListe) {
|
|
275
573
|
if (anlam.ozelliklerListe) {
|
|
276
574
|
for (const ozellik of anlam.ozelliklerListe) {
|
|
277
|
-
|
|
575
|
+
if (ozellik.tur === "3")
|
|
576
|
+
pos.add(ozellik.tam_adi);
|
|
278
577
|
}
|
|
279
578
|
}
|
|
280
579
|
}
|
|
@@ -285,6 +584,116 @@ var TDK = class {
|
|
|
285
584
|
}
|
|
286
585
|
return Array.from(pos);
|
|
287
586
|
}
|
|
587
|
+
/**
|
|
588
|
+
* Compares two words side by side: meaning count, etymological origin,
|
|
589
|
+
* syllables and vowel-harmony compliance.
|
|
590
|
+
*/
|
|
591
|
+
static async compareWords(a, b) {
|
|
592
|
+
const [meaningsA, meaningsB, originA, originB] = await Promise.all([
|
|
593
|
+
this.getMeanings(a),
|
|
594
|
+
this.getMeanings(b),
|
|
595
|
+
this.getOrigin(a),
|
|
596
|
+
this.getOrigin(b)
|
|
597
|
+
]);
|
|
598
|
+
return {
|
|
599
|
+
a: {
|
|
600
|
+
word: a,
|
|
601
|
+
meaningCount: meaningsA.length,
|
|
602
|
+
origin: originA,
|
|
603
|
+
syllables: this.syllabicate(a),
|
|
604
|
+
harmony: this.checkVowelHarmony(a)
|
|
605
|
+
},
|
|
606
|
+
b: {
|
|
607
|
+
word: b,
|
|
608
|
+
meaningCount: meaningsB.length,
|
|
609
|
+
origin: originB,
|
|
610
|
+
syllables: this.syllabicate(b),
|
|
611
|
+
harmony: this.checkVowelHarmony(b)
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
static STOPWORDS = /* @__PURE__ */ new Set([
|
|
616
|
+
"ve",
|
|
617
|
+
"veya",
|
|
618
|
+
"ile",
|
|
619
|
+
"ama",
|
|
620
|
+
"fakat",
|
|
621
|
+
"ancak",
|
|
622
|
+
"de",
|
|
623
|
+
"da",
|
|
624
|
+
"ki",
|
|
625
|
+
"bu",
|
|
626
|
+
"\u015Fu",
|
|
627
|
+
"o",
|
|
628
|
+
"bir",
|
|
629
|
+
"\xE7ok",
|
|
630
|
+
"az",
|
|
631
|
+
"gibi",
|
|
632
|
+
"i\xE7in",
|
|
633
|
+
"mi",
|
|
634
|
+
"m\u0131",
|
|
635
|
+
"mu",
|
|
636
|
+
"m\xFC",
|
|
637
|
+
"ne",
|
|
638
|
+
"her",
|
|
639
|
+
"hi\xE7",
|
|
640
|
+
"ben",
|
|
641
|
+
"sen",
|
|
642
|
+
"biz",
|
|
643
|
+
"siz",
|
|
644
|
+
"onlar",
|
|
645
|
+
"de\u011Fil",
|
|
646
|
+
"bile",
|
|
647
|
+
"diye"
|
|
648
|
+
]);
|
|
649
|
+
static firstMeaning(results) {
|
|
650
|
+
for (const result of results) {
|
|
651
|
+
for (const anlam of result.anlamlarListe ?? []) {
|
|
652
|
+
if (anlam.anlam)
|
|
653
|
+
return anlam.anlam;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Analyzes every distinct word in a text (Turkish stopwords filtered out),
|
|
660
|
+
* returning each word's first meaning and etymological origin if found.
|
|
661
|
+
* Looks each word up individually (throttled), so scales with text length.
|
|
662
|
+
*/
|
|
663
|
+
static async analyzeText(text) {
|
|
664
|
+
const words = text.toLocaleLowerCase("tr-TR").replace(/[^\p{L}\s]/gu, " ").split(/\s+/).filter((w) => w.length > 1 && !this.STOPWORDS.has(w));
|
|
665
|
+
const unique = [...new Set(words)];
|
|
666
|
+
const analyses = [];
|
|
667
|
+
for (const word2 of unique) {
|
|
668
|
+
const results = await this.getWord(word2);
|
|
669
|
+
const found = results.length > 0;
|
|
670
|
+
analyses.push({
|
|
671
|
+
word: word2,
|
|
672
|
+
found,
|
|
673
|
+
meaning: found ? this.firstMeaning(results) : null,
|
|
674
|
+
origin: found ? results[0].lisan || "T\xFCrk\xE7e" : null
|
|
675
|
+
});
|
|
676
|
+
await this.delay(200);
|
|
677
|
+
}
|
|
678
|
+
return analyses;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Classic edit-distance between two strings.
|
|
682
|
+
*/
|
|
683
|
+
static levenshtein(a, b) {
|
|
684
|
+
const dp = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
|
|
685
|
+
for (let i = 0; i <= a.length; i++)
|
|
686
|
+
dp[i][0] = i;
|
|
687
|
+
for (let j = 0; j <= b.length; j++)
|
|
688
|
+
dp[0][j] = j;
|
|
689
|
+
for (let i = 1; i <= a.length; i++) {
|
|
690
|
+
for (let j = 1; j <= b.length; j++) {
|
|
691
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
692
|
+
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return dp[a.length][b.length];
|
|
696
|
+
}
|
|
288
697
|
/**
|
|
289
698
|
* Fetches multiple words concurrently with a small delay to avoid rate limiting.
|
|
290
699
|
*/
|
|
@@ -335,87 +744,276 @@ var TDK = class {
|
|
|
335
744
|
}
|
|
336
745
|
/**
|
|
337
746
|
* Checks if a word follows Turkish Major Vowel Harmony (Büyük Ünlü Uyumu).
|
|
747
|
+
* Normalizes case via the Turkish locale first: a plain case-insensitive
|
|
748
|
+
* regex would fold ASCII "I" to "i", misreading the back vowel "I"
|
|
749
|
+
* (dotless) as the front vowel "i" (dotted).
|
|
338
750
|
*/
|
|
339
751
|
static checkVowelHarmony(word2) {
|
|
340
|
-
const
|
|
341
|
-
const
|
|
342
|
-
const
|
|
343
|
-
const
|
|
752
|
+
const lower = word2.toLocaleLowerCase("tr-TR");
|
|
753
|
+
const backVowels = /[aıou]/;
|
|
754
|
+
const frontVowels = /[eiöü]/;
|
|
755
|
+
const hasBack = backVowels.test(lower);
|
|
756
|
+
const hasFront = frontVowels.test(lower);
|
|
344
757
|
return !(hasBack && hasFront);
|
|
345
758
|
}
|
|
346
759
|
};
|
|
347
760
|
|
|
348
761
|
// src/cli.ts
|
|
349
|
-
var
|
|
762
|
+
var rawArgs = process.argv.slice(2);
|
|
763
|
+
var jsonMode = rawArgs.includes("--json");
|
|
764
|
+
var args = rawArgs.filter((a) => a !== "--json");
|
|
765
|
+
var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
766
|
+
"ara",
|
|
767
|
+
"anlam",
|
|
768
|
+
"koken",
|
|
769
|
+
"ornek",
|
|
770
|
+
"hece",
|
|
771
|
+
"uyum",
|
|
772
|
+
"yazim",
|
|
773
|
+
"gunun",
|
|
774
|
+
"rastgele",
|
|
775
|
+
"esanlam",
|
|
776
|
+
"karsit",
|
|
777
|
+
"yabanci",
|
|
778
|
+
"kurallar",
|
|
779
|
+
"kural",
|
|
780
|
+
"karsilastir",
|
|
781
|
+
"analiz",
|
|
782
|
+
"oneri"
|
|
783
|
+
]);
|
|
350
784
|
var command = args[0];
|
|
351
|
-
var word = args
|
|
785
|
+
var word = args.slice(1).join(" ");
|
|
786
|
+
if (command && !KNOWN_COMMANDS.has(command) && command !== "--help" && command !== "-h") {
|
|
787
|
+
word = args.join(" ");
|
|
788
|
+
command = "anlam";
|
|
789
|
+
}
|
|
790
|
+
function printResult(data, formatted) {
|
|
791
|
+
if (jsonMode) {
|
|
792
|
+
console.log(JSON.stringify(data));
|
|
793
|
+
} else {
|
|
794
|
+
formatted();
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
function printError(message) {
|
|
798
|
+
if (jsonMode) {
|
|
799
|
+
console.log(JSON.stringify({ error: message }));
|
|
800
|
+
} else {
|
|
801
|
+
console.log(`Hata: ${message}`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
352
804
|
async function run() {
|
|
353
|
-
if (!command) {
|
|
354
|
-
console.log("Kullan\u0131m: tdk
|
|
355
|
-
console.log(
|
|
356
|
-
|
|
805
|
+
if (!command || command === "--help" || command === "-h") {
|
|
806
|
+
console.log("Kullan\u0131m: tdk [komut] <kelime> [--json]");
|
|
807
|
+
console.log(
|
|
808
|
+
"Komutlar: ara, anlam, koken, ornek, hece, uyum, yazim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz, oneri"
|
|
809
|
+
);
|
|
810
|
+
console.log("Not: Komut belirtilmezse do\u011Frudan kelime anlam\u0131 aran\u0131r (\xF6rn: tdk selam)");
|
|
811
|
+
process.exit(command ? 0 : 1);
|
|
357
812
|
}
|
|
358
813
|
TDK.enableCache(false);
|
|
359
814
|
try {
|
|
360
815
|
switch (command) {
|
|
361
816
|
case "ara":
|
|
362
|
-
case "anlam":
|
|
817
|
+
case "anlam": {
|
|
363
818
|
if (!word)
|
|
364
819
|
throw new Error("Kelime belirtmelisiniz.");
|
|
365
820
|
const meanings = await TDK.getMeanings(word);
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
821
|
+
printResult(meanings, () => {
|
|
822
|
+
if (meanings.length === 0) {
|
|
823
|
+
console.log("Sonu\xE7 bulunamad\u0131.");
|
|
824
|
+
} else {
|
|
825
|
+
meanings.forEach((m, i) => console.log(`${i + 1}. ${m}`));
|
|
826
|
+
}
|
|
827
|
+
});
|
|
371
828
|
break;
|
|
372
|
-
|
|
829
|
+
}
|
|
830
|
+
case "koken": {
|
|
373
831
|
if (!word)
|
|
374
832
|
throw new Error("Kelime belirtmelisiniz.");
|
|
375
833
|
const origin = await TDK.getOrigin(word);
|
|
376
|
-
console.log(`K\xF6ken: ${origin}`);
|
|
834
|
+
printResult({ word, origin }, () => console.log(`K\xF6ken: ${origin}`));
|
|
377
835
|
break;
|
|
378
|
-
|
|
836
|
+
}
|
|
837
|
+
case "ornek": {
|
|
379
838
|
if (!word)
|
|
380
839
|
throw new Error("Kelime belirtmelisiniz.");
|
|
381
840
|
const examples = await TDK.getExamples(word);
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
841
|
+
printResult(examples, () => {
|
|
842
|
+
if (examples.length === 0) {
|
|
843
|
+
console.log("\xD6rnek bulunamad\u0131.");
|
|
844
|
+
} else {
|
|
845
|
+
examples.forEach((ex, i) => {
|
|
846
|
+
const yazar = ex.author ? ` (${ex.author})` : "";
|
|
847
|
+
console.log(`${i + 1}. ${ex.sentence}${yazar}`);
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
});
|
|
390
851
|
break;
|
|
391
|
-
|
|
852
|
+
}
|
|
853
|
+
case "hece": {
|
|
392
854
|
if (!word)
|
|
393
855
|
throw new Error("Kelime belirtmelisiniz.");
|
|
394
856
|
const syllables = TDK.syllabicate(word);
|
|
395
|
-
console.log(`Heceler: ${syllables.join("-")}`);
|
|
857
|
+
printResult(syllables, () => console.log(`Heceler: ${syllables.join("-")}`));
|
|
396
858
|
break;
|
|
397
|
-
|
|
859
|
+
}
|
|
860
|
+
case "uyum": {
|
|
398
861
|
if (!word)
|
|
399
862
|
throw new Error("Kelime belirtmelisiniz.");
|
|
400
863
|
const isHarmony = TDK.checkVowelHarmony(word);
|
|
401
|
-
|
|
864
|
+
printResult(
|
|
865
|
+
{ word, harmony: isHarmony },
|
|
866
|
+
() => console.log(`B\xFCy\xFCk \xDCnl\xFC Uyumu: ${isHarmony ? "Uyar" : "Uymaz"}`)
|
|
867
|
+
);
|
|
402
868
|
break;
|
|
403
|
-
|
|
869
|
+
}
|
|
870
|
+
case "yazim": {
|
|
404
871
|
if (!word)
|
|
405
872
|
throw new Error("Kelime belirtmelisiniz.");
|
|
406
873
|
const spellResult = await TDK.checkSpelling(word);
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
874
|
+
printResult(spellResult, () => {
|
|
875
|
+
if (spellResult.isCorrect) {
|
|
876
|
+
console.log("Do\u011Fru yaz\u0131m.");
|
|
877
|
+
} else {
|
|
878
|
+
console.log(`Yanl\u0131\u015F yaz\u0131m.${spellResult.suggestion ? " Do\u011Frusu: " + spellResult.suggestion : ""}`);
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
break;
|
|
882
|
+
}
|
|
883
|
+
case "gunun": {
|
|
884
|
+
const wotd = await TDK.getWordOfTheDay();
|
|
885
|
+
printResult(wotd, () => {
|
|
886
|
+
if (!wotd) {
|
|
887
|
+
console.log("G\xFCn\xFCn kelimesi al\u0131namad\u0131.");
|
|
888
|
+
} else {
|
|
889
|
+
console.log(`G\xFCn\xFCn kelimesi: ${wotd.word}`);
|
|
890
|
+
wotd.meanings.forEach((m, i) => console.log(`${i + 1}. ${m}`));
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
break;
|
|
894
|
+
}
|
|
895
|
+
case "rastgele": {
|
|
896
|
+
const pick = await TDK.getRandomWord();
|
|
897
|
+
printResult(pick, () => {
|
|
898
|
+
if (!pick) {
|
|
899
|
+
console.log("Rastgele i\xE7erik al\u0131namad\u0131.");
|
|
900
|
+
} else {
|
|
901
|
+
const label = pick.type === "kelime" ? "Kelime" : "Atas\xF6z\xFC";
|
|
902
|
+
console.log(`${label}: ${pick.madde}`);
|
|
903
|
+
console.log(pick.anlam);
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
break;
|
|
907
|
+
}
|
|
908
|
+
case "esanlam": {
|
|
909
|
+
if (!word)
|
|
910
|
+
throw new Error("Kelime belirtmelisiniz.");
|
|
911
|
+
const synonyms = await TDK.getSynonyms(word);
|
|
912
|
+
printResult(synonyms, () => {
|
|
913
|
+
if (synonyms.length === 0) {
|
|
914
|
+
console.log("E\u015F anlaml\u0131 kelime bulunamad\u0131.");
|
|
915
|
+
} else {
|
|
916
|
+
synonyms.forEach((s, i) => console.log(`${i + 1}. ${s}`));
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
case "karsit": {
|
|
922
|
+
if (!word)
|
|
923
|
+
throw new Error("Kelime belirtmelisiniz.");
|
|
924
|
+
const antonyms = await TDK.getAntonyms(word);
|
|
925
|
+
printResult(antonyms, () => {
|
|
926
|
+
if (antonyms.length === 0) {
|
|
927
|
+
console.log("Z\u0131t anlaml\u0131 kelime bulunamad\u0131.");
|
|
928
|
+
} else {
|
|
929
|
+
antonyms.forEach((s, i) => console.log(`${i + 1}. ${s}`));
|
|
930
|
+
}
|
|
931
|
+
});
|
|
932
|
+
break;
|
|
933
|
+
}
|
|
934
|
+
case "yabanci": {
|
|
935
|
+
if (!word)
|
|
936
|
+
throw new Error("Kelime belirtmelisiniz.");
|
|
937
|
+
const foreign = await TDK.isForeignWord(word);
|
|
938
|
+
printResult({ word, foreign }, () => {
|
|
939
|
+
if (foreign === null) {
|
|
940
|
+
console.log("Kelime bulunamad\u0131.");
|
|
941
|
+
} else {
|
|
942
|
+
console.log(foreign ? "Yabanc\u0131 k\xF6kenli." : "T\xFCrk\xE7e k\xF6kenli.");
|
|
943
|
+
}
|
|
944
|
+
});
|
|
945
|
+
break;
|
|
946
|
+
}
|
|
947
|
+
case "kurallar": {
|
|
948
|
+
const rules = await TDK.getKurallar();
|
|
949
|
+
printResult(rules, () => {
|
|
950
|
+
if (rules.length === 0) {
|
|
951
|
+
console.log("Kural listesi al\u0131namad\u0131.");
|
|
952
|
+
} else {
|
|
953
|
+
rules.forEach((r, i) => console.log(`${i + 1}. ${r.adi}`));
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
break;
|
|
957
|
+
}
|
|
958
|
+
case "kural": {
|
|
959
|
+
if (!word)
|
|
960
|
+
throw new Error("Kural ad\u0131 belirtmelisiniz.");
|
|
961
|
+
const rule = await TDK.getRule(word);
|
|
962
|
+
printResult(rule, () => {
|
|
963
|
+
console.log(rule ?? "Kural bulunamad\u0131.");
|
|
964
|
+
});
|
|
965
|
+
break;
|
|
966
|
+
}
|
|
967
|
+
case "karsilastir": {
|
|
968
|
+
const [wordA, wordB] = args.slice(1);
|
|
969
|
+
if (!wordA || !wordB)
|
|
970
|
+
throw new Error("\u0130ki kelime belirtmelisiniz.");
|
|
971
|
+
const comparison = await TDK.compareWords(wordA, wordB);
|
|
972
|
+
printResult(comparison, () => {
|
|
973
|
+
for (const side of [comparison.a, comparison.b]) {
|
|
974
|
+
console.log(`${side.word}: ${side.meaningCount} anlam, k\xF6ken: ${side.origin ?? "bulunamad\u0131"}, hece: ${side.syllables.join("-")}, b\xFCy\xFCk \xFCnl\xFC uyumu: ${side.harmony ? "uyar" : "uymaz"}`);
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
break;
|
|
978
|
+
}
|
|
979
|
+
case "analiz": {
|
|
980
|
+
if (!word)
|
|
981
|
+
throw new Error("Metin belirtmelisiniz.");
|
|
982
|
+
const analysis = await TDK.analyzeText(word);
|
|
983
|
+
printResult(analysis, () => {
|
|
984
|
+
if (analysis.length === 0) {
|
|
985
|
+
console.log("Analiz edilecek kelime bulunamad\u0131.");
|
|
986
|
+
} else {
|
|
987
|
+
analysis.forEach((a) => {
|
|
988
|
+
if (a.found) {
|
|
989
|
+
console.log(`${a.word}: ${a.meaning ?? "-"} (${a.origin})`);
|
|
990
|
+
} else {
|
|
991
|
+
console.log(`${a.word}: bulunamad\u0131`);
|
|
992
|
+
}
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
});
|
|
412
996
|
break;
|
|
997
|
+
}
|
|
998
|
+
case "oneri": {
|
|
999
|
+
if (!word)
|
|
1000
|
+
throw new Error("\xD6nek belirtmelisiniz.");
|
|
1001
|
+
const suggestions = await TDK.getSuggestions(word);
|
|
1002
|
+
printResult(suggestions, () => {
|
|
1003
|
+
if (suggestions.length === 0) {
|
|
1004
|
+
console.log("\xD6neri bulunamad\u0131.");
|
|
1005
|
+
} else {
|
|
1006
|
+
suggestions.forEach((s, i) => console.log(`${i + 1}. ${s}`));
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
break;
|
|
1010
|
+
}
|
|
413
1011
|
default:
|
|
414
|
-
|
|
1012
|
+
printError("Bilinmeyen komut.");
|
|
415
1013
|
}
|
|
416
1014
|
} catch (error) {
|
|
417
1015
|
if (error instanceof Error) {
|
|
418
|
-
|
|
1016
|
+
printError(error.message);
|
|
419
1017
|
}
|
|
420
1018
|
}
|
|
421
1019
|
}
|