tdk-api-wrapper 1.0.1 → 1.1.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/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 Error("Word parameter cannot be empty.");
91
+ throw new TDKValidationError("Word parameter cannot be empty.");
63
92
  }
64
- const cleanWord = word2.trim().toLowerCase();
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
- const response = await fetch(url, {
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
- if (error instanceof Error)
88
- throw new Error(`Failed to fetch word from TDK: ${error.message}`);
89
- throw new Error("Failed to fetch word from TDK: Unknown error");
104
+ throw new TDKNetworkError("Failed to fetch word from TDK: request failed.", { cause: error });
90
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);
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.
@@ -124,8 +160,8 @@ var TDK = class {
124
160
  return [];
125
161
  }
126
162
  }
127
- const cleanPrefix = prefix.toLowerCase();
128
- return this.autocompleteCache.filter((w) => w.toLowerCase().startsWith(cleanPrefix)).slice(0, 10);
163
+ const cleanPrefix = prefix.toLocaleLowerCase("tr-TR");
164
+ return this.autocompleteCache.filter((w) => w.toLocaleLowerCase("tr-TR").startsWith(cleanPrefix)).slice(0, 10);
129
165
  }
130
166
  /**
131
167
  * Returns a list of proverbs and idioms containing the word.
@@ -146,14 +182,40 @@ var TDK = class {
146
182
  return proverbs;
147
183
  }
148
184
  /**
149
- * Returns the etymological origin of the word if it's a foreign word.
185
+ * Returns the etymological origin of the word, or "Türkçe" if TDK doesn't
186
+ * record a foreign origin for it. Returns `null` only when the word itself
187
+ * isn't found in the dictionary at all.
150
188
  */
151
189
  static async getOrigin(word2) {
152
190
  const results = await this.getWord(word2);
153
- if (results.length > 0 && results[0].lisan) {
154
- return results[0].lisan;
191
+ if (results.length === 0)
192
+ return null;
193
+ return results[0].lisan || "T\xFCrk\xE7e";
194
+ }
195
+ /**
196
+ * Returns whether the word has a recorded foreign etymological origin.
197
+ * Returns `null` (instead of a boolean) when the word isn't found at all.
198
+ */
199
+ static async isForeignWord(word2) {
200
+ const origin = await this.getOrigin(word2);
201
+ if (origin === null)
202
+ return null;
203
+ return origin !== "T\xFCrk\xE7e";
204
+ }
205
+ /**
206
+ * Groups a list of words by their etymological origin. Words not found in
207
+ * the dictionary are grouped under "Bilinmiyor". Throttled like getWordsBatch.
208
+ */
209
+ static async groupByOrigin(words) {
210
+ const groups = {};
211
+ for (const word2 of words) {
212
+ const origin = await this.getOrigin(word2) ?? "Bilinmiyor";
213
+ if (!groups[origin])
214
+ groups[origin] = [];
215
+ groups[origin].push(word2);
216
+ await this.delay(200);
155
217
  }
156
- return "T\xFCrk\xE7e";
218
+ return groups;
157
219
  }
158
220
  /**
159
221
  * Returns literature examples containing the word.
@@ -176,15 +238,108 @@ var TDK = class {
176
238
  return examples;
177
239
  }
178
240
  /**
179
- * Returns the direct URL of the audio pronunciation if available.
180
- * Note: TDK audio URL usually uses the exact audio id. Sometimes it requires MD5, but we provide a common pattern.
241
+ * Calls the `api.sozluk.gov.tr/gts-yeni` endpoint the official web UI uses
242
+ * internally (richer than the public `/gts`: includes `seskod`,
243
+ * `anlamEsAnlam`/`anlamKarsitAnlam`, etc). That endpoint 403s unless the
244
+ * request looks like it came from a browser tab on sozluk.gov.tr: it needs
245
+ * an `Origin`/`Referer` pair matching that site AND a browser-like
246
+ * `User-Agent` (our usual `TDK-API-Nodejs-Wrapper/…` UA gets rejected).
247
+ * `fetch` (undici) also strips a manually-set `Origin` header as a
248
+ * forbidden header name, so this uses `node:https` directly instead.
249
+ * This is inherently fragile scraping of an undocumented endpoint — if
250
+ * TDK tightens this check further, this should fail closed to `null`
251
+ * rather than throw.
252
+ */
253
+ static fetchGtsYeni(word2) {
254
+ return new Promise((resolve) => {
255
+ const req = https.request(
256
+ {
257
+ hostname: this.AUDIO_API_HOST,
258
+ path: `/gts-yeni?ara=${encodeURIComponent(word2)}`,
259
+ method: "GET",
260
+ headers: {
261
+ "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",
262
+ Origin: this.BASE_URL,
263
+ Referer: `${this.BASE_URL}/`
264
+ }
265
+ },
266
+ (res) => {
267
+ let body = "";
268
+ res.on("data", (chunk) => body += chunk);
269
+ res.on("end", () => {
270
+ try {
271
+ const data = JSON.parse(body);
272
+ resolve(Array.isArray(data) ? data : null);
273
+ } catch {
274
+ resolve(null);
275
+ }
276
+ });
277
+ }
278
+ );
279
+ req.on("error", () => resolve(null));
280
+ req.end();
281
+ });
282
+ }
283
+ static async fetchSeskod(word2) {
284
+ const data = await this.fetchGtsYeni(word2);
285
+ const seskod = data?.[0]?.seskod;
286
+ return seskod ? String(seskod) : null;
287
+ }
288
+ /**
289
+ * Returns synonyms ("eş anlamlı kelimeler") recorded for the word, pooled
290
+ * across all of its meanings. Uses the same undocumented `gts-yeni`
291
+ * endpoint as `getAudioUrl` — returns `[]` if the lookup fails.
292
+ */
293
+ static async getSynonyms(word2) {
294
+ if (!word2 || word2.trim() === "")
295
+ return [];
296
+ const data = await this.fetchGtsYeni(word2.trim().toLocaleLowerCase("tr-TR"));
297
+ if (!data)
298
+ return [];
299
+ const synonyms = [];
300
+ for (const entry of data) {
301
+ for (const anlam of entry.anlamlarListe ?? []) {
302
+ for (const es of anlam.anlamEsAnlam ?? []) {
303
+ if (es.deger)
304
+ synonyms.push(es.deger);
305
+ }
306
+ }
307
+ }
308
+ return [...new Set(synonyms)];
309
+ }
310
+ /**
311
+ * Returns antonyms ("zıt anlamlı kelimeler") recorded for the word, pooled
312
+ * across all of its meanings. Uses the same undocumented `gts-yeni`
313
+ * endpoint as `getAudioUrl` — returns `[]` if the lookup fails.
314
+ */
315
+ static async getAntonyms(word2) {
316
+ if (!word2 || word2.trim() === "")
317
+ return [];
318
+ const data = await this.fetchGtsYeni(word2.trim().toLocaleLowerCase("tr-TR"));
319
+ if (!data)
320
+ return [];
321
+ const antonyms = [];
322
+ for (const entry of data) {
323
+ for (const anlam of entry.anlamlarListe ?? []) {
324
+ for (const ka of anlam.anlamKarsitAnlam ?? []) {
325
+ if (ka.deger)
326
+ antonyms.push(ka.deger);
327
+ }
328
+ }
329
+ }
330
+ return [...new Set(antonyms)];
331
+ }
332
+ /**
333
+ * Returns the direct URL of the audio pronunciation, if TDK has one recorded for this word.
181
334
  */
182
335
  static async getAudioUrl(word2) {
183
- const results = await this.getWord(word2);
184
- if (results.length > 0) {
185
- return `https://sozluk.gov.tr/ses/${encodeURIComponent(word2)}.wav`;
336
+ if (!word2 || word2.trim() === "") {
337
+ throw new TDKValidationError("Word parameter cannot be empty.");
186
338
  }
187
- return null;
339
+ const seskod = await this.fetchSeskod(word2.trim().toLocaleLowerCase("tr-TR"));
340
+ if (!seskod)
341
+ return null;
342
+ return `https://${this.AUDIO_API_HOST}/ses/${encodeURIComponent(seskod)}.wav`;
188
343
  }
189
344
  /**
190
345
  * Downloads the audio pronunciation to the specified path.
@@ -215,14 +370,29 @@ var TDK = class {
215
370
  }
216
371
  const daily = await this.getDailyContent();
217
372
  if (daily) {
218
- const syydMatch = daily.syyd.find((s) => s.yanliskelime.toLowerCase() === word2.toLowerCase());
373
+ const syydMatch = daily.syyd.find((s) => s.yanliskelime.toLocaleLowerCase("tr-TR") === word2.toLocaleLowerCase("tr-TR"));
219
374
  if (syydMatch) {
220
375
  return { isCorrect: false, word: word2, suggestion: syydMatch.dogrukelime };
221
376
  }
222
- const mixMatch = daily.karistirma.find((s) => s.yanlis.toLowerCase() === word2.toLowerCase());
377
+ const mixMatch = daily.karistirma.find((s) => s.yanlis.toLocaleLowerCase("tr-TR") === word2.toLocaleLowerCase("tr-TR"));
223
378
  if (mixMatch) {
224
379
  return { isCorrect: false, word: word2, suggestion: mixMatch.dogru };
225
380
  }
381
+ const candidates = [
382
+ ...daily.syyd.map((s) => s.dogrukelime),
383
+ ...daily.karistirma.flatMap((s) => [s.yanlis, s.dogru]),
384
+ ...daily.kelime.map((k) => k.madde)
385
+ ];
386
+ let best = null;
387
+ for (const candidate of candidates) {
388
+ const distance = this.levenshtein(word2.toLocaleLowerCase("tr-TR"), candidate.toLocaleLowerCase("tr-TR"));
389
+ if (distance > 0 && (!best || distance < best.distance)) {
390
+ best = { candidate, distance };
391
+ }
392
+ }
393
+ if (best && best.distance <= 2) {
394
+ return { isCorrect: false, word: word2, suggestion: best.candidate };
395
+ }
226
396
  }
227
397
  return { isCorrect: false, word: word2 };
228
398
  }
@@ -247,6 +417,94 @@ var TDK = class {
247
417
  }
248
418
  return null;
249
419
  }
420
+ /**
421
+ * Returns today's word of the day along with all of its listed meanings.
422
+ */
423
+ static async getWordOfTheDay() {
424
+ const daily = await this.getDailyContent();
425
+ if (!daily || daily.kelime.length === 0)
426
+ return null;
427
+ const word2 = daily.kelime[0].madde;
428
+ const meanings = daily.kelime.filter((k) => k.madde === word2).map((k) => k.anlam);
429
+ return { word: word2, meanings };
430
+ }
431
+ /**
432
+ * Picks a random entry (word or proverb) from today's daily content.
433
+ * Note: this samples from today's `getDailyContent()` picks, not the full dictionary.
434
+ */
435
+ static async getRandomWord() {
436
+ const daily = await this.getDailyContent();
437
+ if (!daily)
438
+ return null;
439
+ const pool = [
440
+ ...daily.kelime.map((k) => ({ type: "kelime", madde: k.madde, anlam: k.anlam })),
441
+ ...daily.atasoz.map((a) => ({ type: "atasoz", madde: a.madde, anlam: a.anlam }))
442
+ ];
443
+ if (pool.length === 0)
444
+ return null;
445
+ return pool[Math.floor(Math.random() * pool.length)];
446
+ }
447
+ /**
448
+ * Returns the spelling-rule page(s) ("yazım kuralları") linked from TDK's
449
+ * `/icerik` daily-content feed, e.g. `{ adi: "Kısaltmalar", url: "https://..." }`.
450
+ * Note: like `getRandomWord()`, this is NOT a fixed catalog — `/icerik`
451
+ * appears to hand back a single randomly-rotated rule per request, so two
452
+ * calls a second apart can return entirely different rules.
453
+ */
454
+ static async getKurallar() {
455
+ const daily = await this.getDailyContent();
456
+ return daily?.kural ?? [];
457
+ }
458
+ /**
459
+ * Fetches the full plain-text content of a named spelling rule (matched
460
+ * case-insensitively, substring match) from `tdk.gov.tr`. Since `/icerik`
461
+ * hands back a single randomly-rotated rule per request (out of a pool of
462
+ * roughly twenty) rather than a fixed catalog, a single `getKurallar()`
463
+ * draw would rarely match a given name — this re-draws (bounded, with a
464
+ * short delay) until it finds a match or gives up. Returns `null` if no
465
+ * match turns up within the attempt budget or the matched page can't be
466
+ * parsed.
467
+ */
468
+ static async getRule(name) {
469
+ if (!name || name.trim() === "")
470
+ return null;
471
+ const target = name.trim().toLocaleLowerCase("tr-TR");
472
+ for (let attempt = 0; attempt < 25; attempt++) {
473
+ const rules = await this.getKurallar();
474
+ const match = rules.find((r) => r.adi.toLocaleLowerCase("tr-TR").includes(target));
475
+ if (match)
476
+ return this.fetchRuleText(match.url);
477
+ await this.delay(100);
478
+ }
479
+ return null;
480
+ }
481
+ /**
482
+ * `tdk.gov.tr` rule pages are WordPress/Avada-themed. The actual article
483
+ * text lives in `<div ... itemprop="text">...</div>` right before a
484
+ * `<footer class="entry...">` (share buttons, author box, structured-data
485
+ * spans) — cutting there avoids that trailing cruft.
486
+ */
487
+ static async fetchRuleText(url) {
488
+ try {
489
+ const response = await fetch(url, { headers: { "User-Agent": "TDK-API-Nodejs-Wrapper/1.0" } });
490
+ if (!response.ok)
491
+ return null;
492
+ const html = await response.text();
493
+ const marker = html.indexOf('itemprop="text"');
494
+ if (marker === -1)
495
+ return null;
496
+ const contentStart = html.indexOf(">", marker) + 1;
497
+ const contentEnd = html.indexOf("<footer", contentStart);
498
+ if (contentEnd === -1)
499
+ return null;
500
+ return this.htmlToPlainText(html.slice(contentStart, contentEnd));
501
+ } catch {
502
+ return null;
503
+ }
504
+ }
505
+ static htmlToPlainText(html) {
506
+ return html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div)>/gi, "\n\n").replace(/<[^>]+>/g, "").replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&quot;/gi, '"').replace(/&#39;|&rsquo;/gi, "'").replace(/[ \t]+/g, " ").replace(/[ \t]*\n[ \t]*/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
507
+ }
250
508
  /**
251
509
  * Returns compound words that contain this word.
252
510
  */
@@ -265,6 +523,9 @@ var TDK = class {
265
523
  }
266
524
  /**
267
525
  * Returns the part of speech (isim, sıfat, zarf vb.).
526
+ * TDK's `ozelliklerListe` mixes grammatical categories (`tur: "3"`, e.g.
527
+ * sıfat/zarf/isim) with usage-register tags (`tur: "4"`, e.g. mecaz/argo)
528
+ * in the same list — only `tur === "3"` entries are actual parts of speech.
268
529
  */
269
530
  static async getPartOfSpeech(word2) {
270
531
  const results = await this.getWord(word2);
@@ -274,7 +535,8 @@ var TDK = class {
274
535
  for (const anlam of result.anlamlarListe) {
275
536
  if (anlam.ozelliklerListe) {
276
537
  for (const ozellik of anlam.ozelliklerListe) {
277
- pos.add(ozellik.tam_adi);
538
+ if (ozellik.tur === "3")
539
+ pos.add(ozellik.tam_adi);
278
540
  }
279
541
  }
280
542
  }
@@ -285,6 +547,116 @@ var TDK = class {
285
547
  }
286
548
  return Array.from(pos);
287
549
  }
550
+ /**
551
+ * Compares two words side by side: meaning count, etymological origin,
552
+ * syllables and vowel-harmony compliance.
553
+ */
554
+ static async compareWords(a, b) {
555
+ const [meaningsA, meaningsB, originA, originB] = await Promise.all([
556
+ this.getMeanings(a),
557
+ this.getMeanings(b),
558
+ this.getOrigin(a),
559
+ this.getOrigin(b)
560
+ ]);
561
+ return {
562
+ a: {
563
+ word: a,
564
+ meaningCount: meaningsA.length,
565
+ origin: originA,
566
+ syllables: this.syllabicate(a),
567
+ harmony: this.checkVowelHarmony(a)
568
+ },
569
+ b: {
570
+ word: b,
571
+ meaningCount: meaningsB.length,
572
+ origin: originB,
573
+ syllables: this.syllabicate(b),
574
+ harmony: this.checkVowelHarmony(b)
575
+ }
576
+ };
577
+ }
578
+ static STOPWORDS = /* @__PURE__ */ new Set([
579
+ "ve",
580
+ "veya",
581
+ "ile",
582
+ "ama",
583
+ "fakat",
584
+ "ancak",
585
+ "de",
586
+ "da",
587
+ "ki",
588
+ "bu",
589
+ "\u015Fu",
590
+ "o",
591
+ "bir",
592
+ "\xE7ok",
593
+ "az",
594
+ "gibi",
595
+ "i\xE7in",
596
+ "mi",
597
+ "m\u0131",
598
+ "mu",
599
+ "m\xFC",
600
+ "ne",
601
+ "her",
602
+ "hi\xE7",
603
+ "ben",
604
+ "sen",
605
+ "biz",
606
+ "siz",
607
+ "onlar",
608
+ "de\u011Fil",
609
+ "bile",
610
+ "diye"
611
+ ]);
612
+ static firstMeaning(results) {
613
+ for (const result of results) {
614
+ for (const anlam of result.anlamlarListe ?? []) {
615
+ if (anlam.anlam)
616
+ return anlam.anlam;
617
+ }
618
+ }
619
+ return null;
620
+ }
621
+ /**
622
+ * Analyzes every distinct word in a text (Turkish stopwords filtered out),
623
+ * returning each word's first meaning and etymological origin if found.
624
+ * Looks each word up individually (throttled), so scales with text length.
625
+ */
626
+ static async analyzeText(text) {
627
+ const words = text.toLocaleLowerCase("tr-TR").replace(/[^\p{L}\s]/gu, " ").split(/\s+/).filter((w) => w.length > 1 && !this.STOPWORDS.has(w));
628
+ const unique = [...new Set(words)];
629
+ const analyses = [];
630
+ for (const word2 of unique) {
631
+ const results = await this.getWord(word2);
632
+ const found = results.length > 0;
633
+ analyses.push({
634
+ word: word2,
635
+ found,
636
+ meaning: found ? this.firstMeaning(results) : null,
637
+ origin: found ? results[0].lisan || "T\xFCrk\xE7e" : null
638
+ });
639
+ await this.delay(200);
640
+ }
641
+ return analyses;
642
+ }
643
+ /**
644
+ * Classic edit-distance between two strings.
645
+ */
646
+ static levenshtein(a, b) {
647
+ const dp = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
648
+ for (let i = 0; i <= a.length; i++)
649
+ dp[i][0] = i;
650
+ for (let j = 0; j <= b.length; j++)
651
+ dp[0][j] = j;
652
+ for (let i = 1; i <= a.length; i++) {
653
+ for (let j = 1; j <= b.length; j++) {
654
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
655
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
656
+ }
657
+ }
658
+ return dp[a.length][b.length];
659
+ }
288
660
  /**
289
661
  * Fetches multiple words concurrently with a small delay to avoid rate limiting.
290
662
  */
@@ -335,87 +707,262 @@ var TDK = class {
335
707
  }
336
708
  /**
337
709
  * Checks if a word follows Turkish Major Vowel Harmony (Büyük Ünlü Uyumu).
710
+ * Normalizes case via the Turkish locale first: a plain case-insensitive
711
+ * regex would fold ASCII "I" to "i", misreading the back vowel "I"
712
+ * (dotless) as the front vowel "i" (dotted).
338
713
  */
339
714
  static checkVowelHarmony(word2) {
340
- const backVowels = /[aıou]/i;
341
- const frontVowels = /[eiöü]/i;
342
- const hasBack = backVowels.test(word2);
343
- const hasFront = frontVowels.test(word2);
715
+ const lower = word2.toLocaleLowerCase("tr-TR");
716
+ const backVowels = /[aıou]/;
717
+ const frontVowels = /[eiöü]/;
718
+ const hasBack = backVowels.test(lower);
719
+ const hasFront = frontVowels.test(lower);
344
720
  return !(hasBack && hasFront);
345
721
  }
346
722
  };
347
723
 
348
724
  // src/cli.ts
349
- var args = process.argv.slice(2);
725
+ var rawArgs = process.argv.slice(2);
726
+ var jsonMode = rawArgs.includes("--json");
727
+ var args = rawArgs.filter((a) => a !== "--json");
728
+ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
729
+ "ara",
730
+ "anlam",
731
+ "koken",
732
+ "ornek",
733
+ "hece",
734
+ "uyum",
735
+ "yazim",
736
+ "gunun",
737
+ "rastgele",
738
+ "esanlam",
739
+ "karsit",
740
+ "yabanci",
741
+ "kurallar",
742
+ "kural",
743
+ "karsilastir",
744
+ "analiz"
745
+ ]);
350
746
  var command = args[0];
351
- var word = args[1];
747
+ var word = args.slice(1).join(" ");
748
+ if (command && !KNOWN_COMMANDS.has(command) && command !== "--help" && command !== "-h") {
749
+ word = args.join(" ");
750
+ command = "anlam";
751
+ }
752
+ function printResult(data, formatted) {
753
+ if (jsonMode) {
754
+ console.log(JSON.stringify(data));
755
+ } else {
756
+ formatted();
757
+ }
758
+ }
759
+ function printError(message) {
760
+ if (jsonMode) {
761
+ console.log(JSON.stringify({ error: message }));
762
+ } else {
763
+ console.log(`Hata: ${message}`);
764
+ }
765
+ }
352
766
  async function run() {
353
- if (!command) {
354
- console.log("Kullan\u0131m: tdk <komut> <kelime>");
355
- console.log("Komutlar: ara, anlam, koken, ornek, hece, uyum, yazim");
356
- process.exit(1);
767
+ if (!command || command === "--help" || command === "-h") {
768
+ console.log("Kullan\u0131m: tdk [komut] <kelime> [--json]");
769
+ console.log(
770
+ "Komutlar: ara, anlam, koken, ornek, hece, uyum, yazim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz"
771
+ );
772
+ console.log("Not: Komut belirtilmezse do\u011Frudan kelime anlam\u0131 aran\u0131r (\xF6rn: tdk selam)");
773
+ process.exit(command ? 0 : 1);
357
774
  }
358
775
  TDK.enableCache(false);
359
776
  try {
360
777
  switch (command) {
361
778
  case "ara":
362
- case "anlam":
779
+ case "anlam": {
363
780
  if (!word)
364
781
  throw new Error("Kelime belirtmelisiniz.");
365
782
  const meanings = await TDK.getMeanings(word);
366
- if (meanings.length === 0) {
367
- console.log("Sonu\xE7 bulunamad\u0131.");
368
- } else {
369
- meanings.forEach((m, i) => console.log(`${i + 1}. ${m}`));
370
- }
783
+ printResult(meanings, () => {
784
+ if (meanings.length === 0) {
785
+ console.log("Sonu\xE7 bulunamad\u0131.");
786
+ } else {
787
+ meanings.forEach((m, i) => console.log(`${i + 1}. ${m}`));
788
+ }
789
+ });
371
790
  break;
372
- case "koken":
791
+ }
792
+ case "koken": {
373
793
  if (!word)
374
794
  throw new Error("Kelime belirtmelisiniz.");
375
795
  const origin = await TDK.getOrigin(word);
376
- console.log(`K\xF6ken: ${origin}`);
796
+ printResult({ word, origin }, () => console.log(`K\xF6ken: ${origin}`));
377
797
  break;
378
- case "ornek":
798
+ }
799
+ case "ornek": {
379
800
  if (!word)
380
801
  throw new Error("Kelime belirtmelisiniz.");
381
802
  const examples = await TDK.getExamples(word);
382
- if (examples.length === 0) {
383
- console.log("\xD6rnek bulunamad\u0131.");
384
- } else {
385
- examples.forEach((ex, i) => {
386
- const yazar = ex.author ? ` (${ex.author})` : "";
387
- console.log(`${i + 1}. ${ex.sentence}${yazar}`);
388
- });
389
- }
803
+ printResult(examples, () => {
804
+ if (examples.length === 0) {
805
+ console.log("\xD6rnek bulunamad\u0131.");
806
+ } else {
807
+ examples.forEach((ex, i) => {
808
+ const yazar = ex.author ? ` (${ex.author})` : "";
809
+ console.log(`${i + 1}. ${ex.sentence}${yazar}`);
810
+ });
811
+ }
812
+ });
390
813
  break;
391
- case "hece":
814
+ }
815
+ case "hece": {
392
816
  if (!word)
393
817
  throw new Error("Kelime belirtmelisiniz.");
394
818
  const syllables = TDK.syllabicate(word);
395
- console.log(`Heceler: ${syllables.join("-")}`);
819
+ printResult(syllables, () => console.log(`Heceler: ${syllables.join("-")}`));
396
820
  break;
397
- case "uyum":
821
+ }
822
+ case "uyum": {
398
823
  if (!word)
399
824
  throw new Error("Kelime belirtmelisiniz.");
400
825
  const isHarmony = TDK.checkVowelHarmony(word);
401
- console.log(`B\xFCy\xFCk \xDCnl\xFC Uyumu: ${isHarmony ? "Uyar" : "Uymaz"}`);
826
+ printResult(
827
+ { word, harmony: isHarmony },
828
+ () => console.log(`B\xFCy\xFCk \xDCnl\xFC Uyumu: ${isHarmony ? "Uyar" : "Uymaz"}`)
829
+ );
402
830
  break;
403
- case "yazim":
831
+ }
832
+ case "yazim": {
404
833
  if (!word)
405
834
  throw new Error("Kelime belirtmelisiniz.");
406
835
  const spellResult = await TDK.checkSpelling(word);
407
- if (spellResult.isCorrect) {
408
- console.log("Do\u011Fru yaz\u0131m.");
409
- } else {
410
- console.log(`Yanl\u0131\u015F yaz\u0131m.${spellResult.suggestion ? " Do\u011Frusu: " + spellResult.suggestion : ""}`);
411
- }
836
+ printResult(spellResult, () => {
837
+ if (spellResult.isCorrect) {
838
+ console.log("Do\u011Fru yaz\u0131m.");
839
+ } else {
840
+ console.log(`Yanl\u0131\u015F yaz\u0131m.${spellResult.suggestion ? " Do\u011Frusu: " + spellResult.suggestion : ""}`);
841
+ }
842
+ });
843
+ break;
844
+ }
845
+ case "gunun": {
846
+ const wotd = await TDK.getWordOfTheDay();
847
+ printResult(wotd, () => {
848
+ if (!wotd) {
849
+ console.log("G\xFCn\xFCn kelimesi al\u0131namad\u0131.");
850
+ } else {
851
+ console.log(`G\xFCn\xFCn kelimesi: ${wotd.word}`);
852
+ wotd.meanings.forEach((m, i) => console.log(`${i + 1}. ${m}`));
853
+ }
854
+ });
855
+ break;
856
+ }
857
+ case "rastgele": {
858
+ const pick = await TDK.getRandomWord();
859
+ printResult(pick, () => {
860
+ if (!pick) {
861
+ console.log("Rastgele i\xE7erik al\u0131namad\u0131.");
862
+ } else {
863
+ const label = pick.type === "kelime" ? "Kelime" : "Atas\xF6z\xFC";
864
+ console.log(`${label}: ${pick.madde}`);
865
+ console.log(pick.anlam);
866
+ }
867
+ });
412
868
  break;
869
+ }
870
+ case "esanlam": {
871
+ if (!word)
872
+ throw new Error("Kelime belirtmelisiniz.");
873
+ const synonyms = await TDK.getSynonyms(word);
874
+ printResult(synonyms, () => {
875
+ if (synonyms.length === 0) {
876
+ console.log("E\u015F anlaml\u0131 kelime bulunamad\u0131.");
877
+ } else {
878
+ synonyms.forEach((s, i) => console.log(`${i + 1}. ${s}`));
879
+ }
880
+ });
881
+ break;
882
+ }
883
+ case "karsit": {
884
+ if (!word)
885
+ throw new Error("Kelime belirtmelisiniz.");
886
+ const antonyms = await TDK.getAntonyms(word);
887
+ printResult(antonyms, () => {
888
+ if (antonyms.length === 0) {
889
+ console.log("Z\u0131t anlaml\u0131 kelime bulunamad\u0131.");
890
+ } else {
891
+ antonyms.forEach((s, i) => console.log(`${i + 1}. ${s}`));
892
+ }
893
+ });
894
+ break;
895
+ }
896
+ case "yabanci": {
897
+ if (!word)
898
+ throw new Error("Kelime belirtmelisiniz.");
899
+ const foreign = await TDK.isForeignWord(word);
900
+ printResult({ word, foreign }, () => {
901
+ if (foreign === null) {
902
+ console.log("Kelime bulunamad\u0131.");
903
+ } else {
904
+ console.log(foreign ? "Yabanc\u0131 k\xF6kenli." : "T\xFCrk\xE7e k\xF6kenli.");
905
+ }
906
+ });
907
+ break;
908
+ }
909
+ case "kurallar": {
910
+ const rules = await TDK.getKurallar();
911
+ printResult(rules, () => {
912
+ if (rules.length === 0) {
913
+ console.log("Kural listesi al\u0131namad\u0131.");
914
+ } else {
915
+ rules.forEach((r, i) => console.log(`${i + 1}. ${r.adi}`));
916
+ }
917
+ });
918
+ break;
919
+ }
920
+ case "kural": {
921
+ if (!word)
922
+ throw new Error("Kural ad\u0131 belirtmelisiniz.");
923
+ const rule = await TDK.getRule(word);
924
+ printResult(rule, () => {
925
+ console.log(rule ?? "Kural bulunamad\u0131.");
926
+ });
927
+ break;
928
+ }
929
+ case "karsilastir": {
930
+ const [wordA, wordB] = args.slice(1);
931
+ if (!wordA || !wordB)
932
+ throw new Error("\u0130ki kelime belirtmelisiniz.");
933
+ const comparison = await TDK.compareWords(wordA, wordB);
934
+ printResult(comparison, () => {
935
+ for (const side of [comparison.a, comparison.b]) {
936
+ 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"}`);
937
+ }
938
+ });
939
+ break;
940
+ }
941
+ case "analiz": {
942
+ if (!word)
943
+ throw new Error("Metin belirtmelisiniz.");
944
+ const analysis = await TDK.analyzeText(word);
945
+ printResult(analysis, () => {
946
+ if (analysis.length === 0) {
947
+ console.log("Analiz edilecek kelime bulunamad\u0131.");
948
+ } else {
949
+ analysis.forEach((a) => {
950
+ if (a.found) {
951
+ console.log(`${a.word}: ${a.meaning ?? "-"} (${a.origin})`);
952
+ } else {
953
+ console.log(`${a.word}: bulunamad\u0131`);
954
+ }
955
+ });
956
+ }
957
+ });
958
+ break;
959
+ }
413
960
  default:
414
- console.log("Bilinmeyen komut.");
961
+ printError("Bilinmeyen komut.");
415
962
  }
416
963
  } catch (error) {
417
964
  if (error instanceof Error) {
418
- console.log(`Hata: ${error.message}`);
965
+ printError(error.message);
419
966
  }
420
967
  }
421
968
  }