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.mjs CHANGED
@@ -1,79 +1,250 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TDK
4
- } from "./chunk-MGSXCUAX.mjs";
4
+ } from "./chunk-P3GX7I53.mjs";
5
5
 
6
6
  // src/cli.ts
7
- var args = process.argv.slice(2);
7
+ var rawArgs = process.argv.slice(2);
8
+ var jsonMode = rawArgs.includes("--json");
9
+ var args = rawArgs.filter((a) => a !== "--json");
10
+ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
11
+ "ara",
12
+ "anlam",
13
+ "koken",
14
+ "ornek",
15
+ "hece",
16
+ "uyum",
17
+ "yazim",
18
+ "gunun",
19
+ "rastgele",
20
+ "esanlam",
21
+ "karsit",
22
+ "yabanci",
23
+ "kurallar",
24
+ "kural",
25
+ "karsilastir",
26
+ "analiz"
27
+ ]);
8
28
  var command = args[0];
9
- var word = args[1];
29
+ var word = args.slice(1).join(" ");
30
+ if (command && !KNOWN_COMMANDS.has(command) && command !== "--help" && command !== "-h") {
31
+ word = args.join(" ");
32
+ command = "anlam";
33
+ }
34
+ function printResult(data, formatted) {
35
+ if (jsonMode) {
36
+ console.log(JSON.stringify(data));
37
+ } else {
38
+ formatted();
39
+ }
40
+ }
41
+ function printError(message) {
42
+ if (jsonMode) {
43
+ console.log(JSON.stringify({ error: message }));
44
+ } else {
45
+ console.log(`Hata: ${message}`);
46
+ }
47
+ }
10
48
  async function run() {
11
- if (!command) {
12
- console.log("Kullan\u0131m: tdk <komut> <kelime>");
13
- console.log("Komutlar: ara, anlam, koken, ornek, hece, uyum, yazim");
14
- process.exit(1);
49
+ if (!command || command === "--help" || command === "-h") {
50
+ console.log("Kullan\u0131m: tdk [komut] <kelime> [--json]");
51
+ console.log(
52
+ "Komutlar: ara, anlam, koken, ornek, hece, uyum, yazim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz"
53
+ );
54
+ console.log("Not: Komut belirtilmezse do\u011Frudan kelime anlam\u0131 aran\u0131r (\xF6rn: tdk selam)");
55
+ process.exit(command ? 0 : 1);
15
56
  }
16
57
  TDK.enableCache(false);
17
58
  try {
18
59
  switch (command) {
19
60
  case "ara":
20
- case "anlam":
61
+ case "anlam": {
21
62
  if (!word)
22
63
  throw new Error("Kelime belirtmelisiniz.");
23
64
  const meanings = await TDK.getMeanings(word);
24
- if (meanings.length === 0) {
25
- console.log("Sonu\xE7 bulunamad\u0131.");
26
- } else {
27
- meanings.forEach((m, i) => console.log(`${i + 1}. ${m}`));
28
- }
65
+ printResult(meanings, () => {
66
+ if (meanings.length === 0) {
67
+ console.log("Sonu\xE7 bulunamad\u0131.");
68
+ } else {
69
+ meanings.forEach((m, i) => console.log(`${i + 1}. ${m}`));
70
+ }
71
+ });
29
72
  break;
30
- case "koken":
73
+ }
74
+ case "koken": {
31
75
  if (!word)
32
76
  throw new Error("Kelime belirtmelisiniz.");
33
77
  const origin = await TDK.getOrigin(word);
34
- console.log(`K\xF6ken: ${origin}`);
78
+ printResult({ word, origin }, () => console.log(`K\xF6ken: ${origin}`));
35
79
  break;
36
- case "ornek":
80
+ }
81
+ case "ornek": {
37
82
  if (!word)
38
83
  throw new Error("Kelime belirtmelisiniz.");
39
84
  const examples = await TDK.getExamples(word);
40
- if (examples.length === 0) {
41
- console.log("\xD6rnek bulunamad\u0131.");
42
- } else {
43
- examples.forEach((ex, i) => {
44
- const yazar = ex.author ? ` (${ex.author})` : "";
45
- console.log(`${i + 1}. ${ex.sentence}${yazar}`);
46
- });
47
- }
85
+ printResult(examples, () => {
86
+ if (examples.length === 0) {
87
+ console.log("\xD6rnek bulunamad\u0131.");
88
+ } else {
89
+ examples.forEach((ex, i) => {
90
+ const yazar = ex.author ? ` (${ex.author})` : "";
91
+ console.log(`${i + 1}. ${ex.sentence}${yazar}`);
92
+ });
93
+ }
94
+ });
48
95
  break;
49
- case "hece":
96
+ }
97
+ case "hece": {
50
98
  if (!word)
51
99
  throw new Error("Kelime belirtmelisiniz.");
52
100
  const syllables = TDK.syllabicate(word);
53
- console.log(`Heceler: ${syllables.join("-")}`);
101
+ printResult(syllables, () => console.log(`Heceler: ${syllables.join("-")}`));
54
102
  break;
55
- case "uyum":
103
+ }
104
+ case "uyum": {
56
105
  if (!word)
57
106
  throw new Error("Kelime belirtmelisiniz.");
58
107
  const isHarmony = TDK.checkVowelHarmony(word);
59
- console.log(`B\xFCy\xFCk \xDCnl\xFC Uyumu: ${isHarmony ? "Uyar" : "Uymaz"}`);
108
+ printResult(
109
+ { word, harmony: isHarmony },
110
+ () => console.log(`B\xFCy\xFCk \xDCnl\xFC Uyumu: ${isHarmony ? "Uyar" : "Uymaz"}`)
111
+ );
60
112
  break;
61
- case "yazim":
113
+ }
114
+ case "yazim": {
62
115
  if (!word)
63
116
  throw new Error("Kelime belirtmelisiniz.");
64
117
  const spellResult = await TDK.checkSpelling(word);
65
- if (spellResult.isCorrect) {
66
- console.log("Do\u011Fru yaz\u0131m.");
67
- } else {
68
- console.log(`Yanl\u0131\u015F yaz\u0131m.${spellResult.suggestion ? " Do\u011Frusu: " + spellResult.suggestion : ""}`);
69
- }
118
+ printResult(spellResult, () => {
119
+ if (spellResult.isCorrect) {
120
+ console.log("Do\u011Fru yaz\u0131m.");
121
+ } else {
122
+ console.log(`Yanl\u0131\u015F yaz\u0131m.${spellResult.suggestion ? " Do\u011Frusu: " + spellResult.suggestion : ""}`);
123
+ }
124
+ });
125
+ break;
126
+ }
127
+ case "gunun": {
128
+ const wotd = await TDK.getWordOfTheDay();
129
+ printResult(wotd, () => {
130
+ if (!wotd) {
131
+ console.log("G\xFCn\xFCn kelimesi al\u0131namad\u0131.");
132
+ } else {
133
+ console.log(`G\xFCn\xFCn kelimesi: ${wotd.word}`);
134
+ wotd.meanings.forEach((m, i) => console.log(`${i + 1}. ${m}`));
135
+ }
136
+ });
137
+ break;
138
+ }
139
+ case "rastgele": {
140
+ const pick = await TDK.getRandomWord();
141
+ printResult(pick, () => {
142
+ if (!pick) {
143
+ console.log("Rastgele i\xE7erik al\u0131namad\u0131.");
144
+ } else {
145
+ const label = pick.type === "kelime" ? "Kelime" : "Atas\xF6z\xFC";
146
+ console.log(`${label}: ${pick.madde}`);
147
+ console.log(pick.anlam);
148
+ }
149
+ });
150
+ break;
151
+ }
152
+ case "esanlam": {
153
+ if (!word)
154
+ throw new Error("Kelime belirtmelisiniz.");
155
+ const synonyms = await TDK.getSynonyms(word);
156
+ printResult(synonyms, () => {
157
+ if (synonyms.length === 0) {
158
+ console.log("E\u015F anlaml\u0131 kelime bulunamad\u0131.");
159
+ } else {
160
+ synonyms.forEach((s, i) => console.log(`${i + 1}. ${s}`));
161
+ }
162
+ });
163
+ break;
164
+ }
165
+ case "karsit": {
166
+ if (!word)
167
+ throw new Error("Kelime belirtmelisiniz.");
168
+ const antonyms = await TDK.getAntonyms(word);
169
+ printResult(antonyms, () => {
170
+ if (antonyms.length === 0) {
171
+ console.log("Z\u0131t anlaml\u0131 kelime bulunamad\u0131.");
172
+ } else {
173
+ antonyms.forEach((s, i) => console.log(`${i + 1}. ${s}`));
174
+ }
175
+ });
176
+ break;
177
+ }
178
+ case "yabanci": {
179
+ if (!word)
180
+ throw new Error("Kelime belirtmelisiniz.");
181
+ const foreign = await TDK.isForeignWord(word);
182
+ printResult({ word, foreign }, () => {
183
+ if (foreign === null) {
184
+ console.log("Kelime bulunamad\u0131.");
185
+ } else {
186
+ console.log(foreign ? "Yabanc\u0131 k\xF6kenli." : "T\xFCrk\xE7e k\xF6kenli.");
187
+ }
188
+ });
189
+ break;
190
+ }
191
+ case "kurallar": {
192
+ const rules = await TDK.getKurallar();
193
+ printResult(rules, () => {
194
+ if (rules.length === 0) {
195
+ console.log("Kural listesi al\u0131namad\u0131.");
196
+ } else {
197
+ rules.forEach((r, i) => console.log(`${i + 1}. ${r.adi}`));
198
+ }
199
+ });
200
+ break;
201
+ }
202
+ case "kural": {
203
+ if (!word)
204
+ throw new Error("Kural ad\u0131 belirtmelisiniz.");
205
+ const rule = await TDK.getRule(word);
206
+ printResult(rule, () => {
207
+ console.log(rule ?? "Kural bulunamad\u0131.");
208
+ });
209
+ break;
210
+ }
211
+ case "karsilastir": {
212
+ const [wordA, wordB] = args.slice(1);
213
+ if (!wordA || !wordB)
214
+ throw new Error("\u0130ki kelime belirtmelisiniz.");
215
+ const comparison = await TDK.compareWords(wordA, wordB);
216
+ printResult(comparison, () => {
217
+ for (const side of [comparison.a, comparison.b]) {
218
+ 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"}`);
219
+ }
220
+ });
221
+ break;
222
+ }
223
+ case "analiz": {
224
+ if (!word)
225
+ throw new Error("Metin belirtmelisiniz.");
226
+ const analysis = await TDK.analyzeText(word);
227
+ printResult(analysis, () => {
228
+ if (analysis.length === 0) {
229
+ console.log("Analiz edilecek kelime bulunamad\u0131.");
230
+ } else {
231
+ analysis.forEach((a) => {
232
+ if (a.found) {
233
+ console.log(`${a.word}: ${a.meaning ?? "-"} (${a.origin})`);
234
+ } else {
235
+ console.log(`${a.word}: bulunamad\u0131`);
236
+ }
237
+ });
238
+ }
239
+ });
70
240
  break;
241
+ }
71
242
  default:
72
- console.log("Bilinmeyen komut.");
243
+ printError("Bilinmeyen komut.");
73
244
  }
74
245
  } catch (error) {
75
246
  if (error instanceof Error) {
76
- console.log(`Hata: ${error.message}`);
247
+ printError(error.message);
77
248
  }
78
249
  }
79
250
  }
package/dist/index.d.mts CHANGED
@@ -96,6 +96,36 @@ interface SpellCheckResult {
96
96
  word: string;
97
97
  suggestion?: string;
98
98
  }
99
+ interface WordOfTheDay {
100
+ word: string;
101
+ meanings: string[];
102
+ }
103
+ interface DailyPick {
104
+ type: "kelime" | "atasoz";
105
+ madde: string;
106
+ anlam: string;
107
+ }
108
+ interface TDKRule {
109
+ adi: string;
110
+ url: string;
111
+ }
112
+ interface WordComparisonSide {
113
+ word: string;
114
+ meaningCount: number;
115
+ origin: string | null;
116
+ syllables: string[];
117
+ harmony: boolean;
118
+ }
119
+ interface WordComparison {
120
+ a: WordComparisonSide;
121
+ b: WordComparisonSide;
122
+ }
123
+ interface WordAnalysis {
124
+ word: string;
125
+ found: boolean;
126
+ meaning: string | null;
127
+ origin: string | null;
128
+ }
99
129
  type TDKResponse = WordInfo[] | {
100
130
  error: string;
101
131
  };
@@ -105,6 +135,7 @@ type TDKResponse = WordInfo[] | {
105
135
  */
106
136
  declare class TDK {
107
137
  private static readonly BASE_URL;
138
+ private static readonly AUDIO_API_HOST;
108
139
  private static isCacheEnabled;
109
140
  private static wordCache;
110
141
  private static dailyContentCache;
@@ -135,9 +166,21 @@ declare class TDK {
135
166
  */
136
167
  static getProverbs(word: string): Promise<string[]>;
137
168
  /**
138
- * Returns the etymological origin of the word if it's a foreign word.
169
+ * Returns the etymological origin of the word, or "Türkçe" if TDK doesn't
170
+ * record a foreign origin for it. Returns `null` only when the word itself
171
+ * isn't found in the dictionary at all.
139
172
  */
140
173
  static getOrigin(word: string): Promise<string | null>;
174
+ /**
175
+ * Returns whether the word has a recorded foreign etymological origin.
176
+ * Returns `null` (instead of a boolean) when the word isn't found at all.
177
+ */
178
+ static isForeignWord(word: string): Promise<boolean | null>;
179
+ /**
180
+ * Groups a list of words by their etymological origin. Words not found in
181
+ * the dictionary are grouped under "Bilinmiyor". Throttled like getWordsBatch.
182
+ */
183
+ static groupByOrigin(words: string[]): Promise<Record<string, string[]>>;
141
184
  /**
142
185
  * Returns literature examples containing the word.
143
186
  */
@@ -146,8 +189,34 @@ declare class TDK {
146
189
  author: string | null;
147
190
  }[]>;
148
191
  /**
149
- * Returns the direct URL of the audio pronunciation if available.
150
- * Note: TDK audio URL usually uses the exact audio id. Sometimes it requires MD5, but we provide a common pattern.
192
+ * Calls the `api.sozluk.gov.tr/gts-yeni` endpoint the official web UI uses
193
+ * internally (richer than the public `/gts`: includes `seskod`,
194
+ * `anlamEsAnlam`/`anlamKarsitAnlam`, etc). That endpoint 403s unless the
195
+ * request looks like it came from a browser tab on sozluk.gov.tr: it needs
196
+ * an `Origin`/`Referer` pair matching that site AND a browser-like
197
+ * `User-Agent` (our usual `TDK-API-Nodejs-Wrapper/…` UA gets rejected).
198
+ * `fetch` (undici) also strips a manually-set `Origin` header as a
199
+ * forbidden header name, so this uses `node:https` directly instead.
200
+ * This is inherently fragile scraping of an undocumented endpoint — if
201
+ * TDK tightens this check further, this should fail closed to `null`
202
+ * rather than throw.
203
+ */
204
+ private static fetchGtsYeni;
205
+ private static fetchSeskod;
206
+ /**
207
+ * Returns synonyms ("eş anlamlı kelimeler") recorded for the word, pooled
208
+ * across all of its meanings. Uses the same undocumented `gts-yeni`
209
+ * endpoint as `getAudioUrl` — returns `[]` if the lookup fails.
210
+ */
211
+ static getSynonyms(word: string): Promise<string[]>;
212
+ /**
213
+ * Returns antonyms ("zıt anlamlı kelimeler") recorded for the word, pooled
214
+ * across all of its meanings. Uses the same undocumented `gts-yeni`
215
+ * endpoint as `getAudioUrl` — returns `[]` if the lookup fails.
216
+ */
217
+ static getAntonyms(word: string): Promise<string[]>;
218
+ /**
219
+ * Returns the direct URL of the audio pronunciation, if TDK has one recorded for this word.
151
220
  */
152
221
  static getAudioUrl(word: string): Promise<string | null>;
153
222
  /**
@@ -162,14 +231,70 @@ declare class TDK {
162
231
  * Fetches daily content (word of the day, proverbs, rules, etc).
163
232
  */
164
233
  static getDailyContent(): Promise<DailyContent | null>;
234
+ /**
235
+ * Returns today's word of the day along with all of its listed meanings.
236
+ */
237
+ static getWordOfTheDay(): Promise<WordOfTheDay | null>;
238
+ /**
239
+ * Picks a random entry (word or proverb) from today's daily content.
240
+ * Note: this samples from today's `getDailyContent()` picks, not the full dictionary.
241
+ */
242
+ static getRandomWord(): Promise<DailyPick | null>;
243
+ /**
244
+ * Returns the spelling-rule page(s) ("yazım kuralları") linked from TDK's
245
+ * `/icerik` daily-content feed, e.g. `{ adi: "Kısaltmalar", url: "https://..." }`.
246
+ * Note: like `getRandomWord()`, this is NOT a fixed catalog — `/icerik`
247
+ * appears to hand back a single randomly-rotated rule per request, so two
248
+ * calls a second apart can return entirely different rules.
249
+ */
250
+ static getKurallar(): Promise<TDKRule[]>;
251
+ /**
252
+ * Fetches the full plain-text content of a named spelling rule (matched
253
+ * case-insensitively, substring match) from `tdk.gov.tr`. Since `/icerik`
254
+ * hands back a single randomly-rotated rule per request (out of a pool of
255
+ * roughly twenty) rather than a fixed catalog, a single `getKurallar()`
256
+ * draw would rarely match a given name — this re-draws (bounded, with a
257
+ * short delay) until it finds a match or gives up. Returns `null` if no
258
+ * match turns up within the attempt budget or the matched page can't be
259
+ * parsed.
260
+ */
261
+ static getRule(name: string): Promise<string | null>;
262
+ /**
263
+ * `tdk.gov.tr` rule pages are WordPress/Avada-themed. The actual article
264
+ * text lives in `<div ... itemprop="text">...</div>` right before a
265
+ * `<footer class="entry...">` (share buttons, author box, structured-data
266
+ * spans) — cutting there avoids that trailing cruft.
267
+ */
268
+ private static fetchRuleText;
269
+ private static htmlToPlainText;
165
270
  /**
166
271
  * Returns compound words that contain this word.
167
272
  */
168
273
  static getCompoundWords(word: string): Promise<string[]>;
169
274
  /**
170
275
  * Returns the part of speech (isim, sıfat, zarf vb.).
276
+ * TDK's `ozelliklerListe` mixes grammatical categories (`tur: "3"`, e.g.
277
+ * sıfat/zarf/isim) with usage-register tags (`tur: "4"`, e.g. mecaz/argo)
278
+ * in the same list — only `tur === "3"` entries are actual parts of speech.
171
279
  */
172
280
  static getPartOfSpeech(word: string): Promise<string[]>;
281
+ /**
282
+ * Compares two words side by side: meaning count, etymological origin,
283
+ * syllables and vowel-harmony compliance.
284
+ */
285
+ static compareWords(a: string, b: string): Promise<WordComparison>;
286
+ private static readonly STOPWORDS;
287
+ private static firstMeaning;
288
+ /**
289
+ * Analyzes every distinct word in a text (Turkish stopwords filtered out),
290
+ * returning each word's first meaning and etymological origin if found.
291
+ * Looks each word up individually (throttled), so scales with text length.
292
+ */
293
+ static analyzeText(text: string): Promise<WordAnalysis[]>;
294
+ /**
295
+ * Classic edit-distance between two strings.
296
+ */
297
+ private static levenshtein;
173
298
  /**
174
299
  * Fetches multiple words concurrently with a small delay to avoid rate limiting.
175
300
  */
@@ -180,8 +305,36 @@ declare class TDK {
180
305
  static syllabicate(word: string): string[];
181
306
  /**
182
307
  * Checks if a word follows Turkish Major Vowel Harmony (Büyük Ünlü Uyumu).
308
+ * Normalizes case via the Turkish locale first: a plain case-insensitive
309
+ * regex would fold ASCII "I" to "i", misreading the back vowel "I"
310
+ * (dotless) as the front vowel "i" (dotted).
183
311
  */
184
312
  static checkVowelHarmony(word: string): boolean;
185
313
  }
186
314
 
187
- export { type Author, type DailyContent, type Example, type Feature, type Meaning, type Proverb, type SpellCheckResult, TDK, type TDKResponse, type WordInfo };
315
+ /**
316
+ * Base class for all errors thrown by this library.
317
+ */
318
+ declare class TDKError extends Error {
319
+ constructor(message: string);
320
+ }
321
+ /**
322
+ * Thrown when a caller-supplied argument is invalid (e.g. an empty word).
323
+ */
324
+ declare class TDKValidationError extends TDKError {
325
+ constructor(message: string);
326
+ }
327
+ /**
328
+ * Thrown when a request to sozluk.gov.tr fails at the network/HTTP level
329
+ * (connection failure, non-OK HTTP status, unparsable response, etc).
330
+ */
331
+ declare class TDKNetworkError extends TDKError {
332
+ readonly status?: number;
333
+ readonly cause?: unknown;
334
+ constructor(message: string, options?: {
335
+ status?: number;
336
+ cause?: unknown;
337
+ });
338
+ }
339
+
340
+ export { type Author, type DailyContent, type DailyPick, type Example, type Feature, type Meaning, type Proverb, type SpellCheckResult, TDK, TDKError, TDKNetworkError, type TDKResponse, type TDKRule, TDKValidationError, type WordAnalysis, type WordComparison, type WordComparisonSide, type WordInfo, type WordOfTheDay };