tdk-api-wrapper 1.6.1 → 1.7.1
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/LICENSE +674 -21
- package/README.md +23 -3
- package/dist/{chunk-ZFWZXNGY.mjs → chunk-YY4WA2AO.mjs} +126 -142
- package/dist/chunk-ZG7YRT7V.mjs +279 -0
- package/dist/cli.js +2489 -2127
- package/dist/cli.mjs +12 -5
- package/dist/index.d.mts +23 -38
- package/dist/index.d.ts +23 -38
- package/dist/index.js +402 -143
- package/dist/index.mjs +8 -2
- package/dist/mcp-GBTXQJLW.mjs +9 -0
- package/package.json +13 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# TDK API Node.js Wrapper
|
|
2
2
|
|
|
3
|
-
Bu proje, Türk Dil Kurumu (TDK) sözlük verilerine Node.js ortamından doğrudan, hızlı ve güvenilir bir şekilde erişim sağlamak amacıyla geliştirilmiş, TypeScript tabanlı resmî olmayan bir sarmalayıcı (wrapper) kütüphanedir. Herhangi bir dış bağımlılığa veya ikili (binary) dosyaya ihtiyaç duymadan HTTP üzerinden güncel verileri çeker.
|
|
3
|
+
Bu proje, Türk Dil Kurumu (TDK) sözlük verilerine Node.js ortamından doğrudan, hızlı ve güvenilir bir şekilde erişim sağlamak amacıyla geliştirilmiş, TypeScript tabanlı resmî olmayan bir sarmalayıcı (wrapper) kütüphanedir. Herhangi bir dış bağımlılığa veya ikili (binary) dosyaya ihtiyaç duymadan HTTP üzerinden güncel verileri çeker. Kütüphanenin yanı sıra bir terminal CLI'si (`tdk`) ve dahili bir **Model Context Protocol (MCP)** sunucusu da paketle birlikte gelir.
|
|
4
4
|
|
|
5
5
|
## Kurulum
|
|
6
6
|
|
|
@@ -46,6 +46,7 @@ tdk repl
|
|
|
46
46
|
tdk kubbealti merhaba
|
|
47
47
|
tdk nisanyan merhaba
|
|
48
48
|
tdk viki merhaba
|
|
49
|
+
tdk mcp # MCP (Model Context Protocol) stdio sunucusunu başlatır
|
|
49
50
|
```
|
|
50
51
|
|
|
51
52
|
Herhangi bir komuta `--json` bayrağı eklendiğinde çıktı, insan-okunur metin yerine tek satırlık JSON olarak basılır (script/otomasyon kullanımı için):
|
|
@@ -90,7 +91,7 @@ Aşağıdaki metotlar `TDK` sınıfı üzerinden statik olarak veya `TDKClient`
|
|
|
90
91
|
- **`TDK.checkVowelHarmony(word)`**: Kelimenin büyük ünlü uyumuna uyup uymadığını (boolean) kontrol eder.
|
|
91
92
|
- **`TDK.checkLabialHarmony(word)`**: Kelimenin küçük ünlü uyumuna (düzlük-yuvarlaklık uyumu) uyup uymadığını (boolean) kontrol eder.
|
|
92
93
|
- **`TDK.getPartOfSpeech(word)`**: Kelimenin sözcük türünü (isim, sıfat, zarf vb.) döndürür.
|
|
93
|
-
- **`TDK.checkSpelling(word)`**: Kelimenin doğru yazılıp yazılmadığını kontrol eder. Önce TDK'de doğrudan arar; bulamazsa
|
|
94
|
+
- **`TDK.checkSpelling(word)`**: Kelimenin doğru yazılıp yazılmadığını kontrol eder. Önce TDK'de doğrudan arar; bulamazsa kütüphanenin dahili sık yapılan yanlışlar listesini (`COMMON_MISSPELLINGS` — [referans liste](https://gist.github.com/cemyilmazzzz122/e4bdd509fe9b03a3070a685ab603aaa8)) kontrol eder. Ardından **morfolojik ek sıyırma (stemming) motoru** devreye girer; kelime çekimli bir biçimse (`halılarımızın`, `kitabımız`, `çocuğa`, `okuyoruz`, `hakkımızda`, `başlıyor`) kökünü tespit edip `{ isCorrect: true, isInflected: true, root: "..." }` döner. Son aşamada ise TDK'nin ~81 bin kelimelik tam madde listesi üzerinde **klavye ve düzeltme işareti farkındalıklı** bir Damerau-Levenshtein edit-distance ile en yakın kelimeyi önerir: Türkçe Q klavyede yan yana duran tuşlar (`arabs` → `araba`, `swlam` → `selam`) ve ASCII/Türkçe harf ikilileri (`ı/i`, `ş/s`, `ö/o` …) tam bir düzeltme yerine bir düzeltmenin küçük bir kesri kadar sayılır; böylece hem en yakın hem de en olası madde kazanır (`yanlız` → `yalnız`).
|
|
94
95
|
- **`TDK.getCompoundWords(word)`**: Aranan kelime ile oluşturulmuş birleşik kelimeleri (Örn: dolma kalem) listeler.
|
|
95
96
|
|
|
96
97
|
### 3. Morfoloji ve Kök Bulma (Morphology Engine)
|
|
@@ -138,6 +139,25 @@ TDK dışındaki bu üç kaynak da her zaman kullanılabilir/dokümante edilmiş
|
|
|
138
139
|
- **`TDK.getNisanyan(word)`**: Nişanyan Sözlük'ten kelimenin etimoloji paragrafını düz metin olarak döner; kelime bulunamazsa `null`.
|
|
139
140
|
- **`TDK.getWiktionary(word)`**: Türkçe Vikisözlük'ten (`tr.wiktionary.org`) resmî MediaWiki API'si (`action=query&prop=extracts`) üzerinden veri çeker — bu üçü arasında scraping olmayan, resmî ve en kararlı olanı. `{ raw, sections }` döner; `sections` metni `== Köken ==`, `=== Söyleniş ===` gibi başlıklara göre bir sözlüğe ayırır. `getWiktionarySection(word, sectionName)` tek bir bölümü (örn. `"Köken"`) büyük/küçük harf duyarsız süzer. Bu wiki'de başlık büyütme kapalı (`$wgCapitalLinks=false` — "Türkiye" ile bir küçük harfli kelime ayrı sayfalardır), o yüzden `TDK.getWiktionary("türkiye")` gibi tam eşleşmeyen aramalar otomatik olarak ilk harfi (Türkçe kurallarına göre, örn. `istanbul` → `İstanbul`) büyütülmüş hâliyle tekrar denenir.
|
|
140
141
|
|
|
142
|
+
## Model Context Protocol (MCP) Sunucusu
|
|
143
|
+
|
|
144
|
+
Paket, bir MCP stdio sunucusu içerir; böylece TDK sözlük, morfoloji, yazım denetimi ve etimoloji araçlarını Claude Desktop, Cursor, Antigravity veya herhangi bir MCP istemcisine yalnızca Node.js ile bağlayabilirsiniz — Python ya da ek bir çalışma zamanı gerekmez.
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"mcpServers": {
|
|
149
|
+
"tdk": {
|
|
150
|
+
"command": "npx",
|
|
151
|
+
"args": ["-y", "tdk-api-wrapper", "mcp"]
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Global kuruluysa `tdk mcp` (veya `tdk-mcp`) komutu da aynı sunucuyu başlatır. Sunucu programatik olarak dışa aktarılan `createMcpServer()` / `runMcpServer()` ile de gömülebilir.
|
|
158
|
+
|
|
159
|
+
Sunulan araçlar: `tdk_lookup`, `tdk_meanings`, `tdk_examples`, `tdk_proverbs`, `tdk_compound_words`, `tdk_part_of_speech`, `tdk_synonyms`, `tdk_antonyms`, `tdk_origin`, `tdk_nisanyan`, `tdk_kubbealti`, `tdk_wiktionary`, `tdk_spell_check`, `tdk_proofread`, `tdk_stem`, `tdk_analyze_text`, `tdk_syllables`, `tdk_vowel_harmony`, `tdk_autocomplete`, `tdk_pattern_search`, `tdk_anagram`, `tdk_rhymes`, `tdk_compare`, `tdk_audio_url`, `tdk_word_of_the_day`, `tdk_random_word`, `tdk_rules`. Her araç JSON metin döndürür; ağ/scraping hataları fırlatmak yerine `isError: true` ile `{ "error": ... }` olarak döner.
|
|
160
|
+
|
|
141
161
|
## Hata Yönetimi
|
|
142
162
|
|
|
143
163
|
Kütüphane, ayırt edilebilir hata sınıfları fırlatır (hepsi `Error`'dan türer):
|
|
@@ -161,4 +181,4 @@ try {
|
|
|
161
181
|
|
|
162
182
|
## Lisans
|
|
163
183
|
|
|
164
|
-
Bu proje
|
|
184
|
+
Bu proje GNU General Public License v3.0 (or later) ile lisanslanmıştır. Kullanım hakları ve kısıtlamalar için kaynak kod içerisindeki [LICENSE](./LICENSE) dosyasını inceleyebilirsiniz.
|
|
@@ -25,11 +25,7 @@ var TDKNetworkError = class extends TDKError {
|
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
// src/
|
|
29
|
-
var TURKISH_VOWELS = "ae\u0131io\xF6u\xFC";
|
|
30
|
-
function isVowel(ch) {
|
|
31
|
-
return TURKISH_VOWELS.includes(ch);
|
|
32
|
-
}
|
|
28
|
+
// src/data/suffixes.ts
|
|
33
29
|
var TURKISH_SUFFIXES = [
|
|
34
30
|
// 9-letter composite suffixes
|
|
35
31
|
"lerimizden",
|
|
@@ -432,6 +428,12 @@ var TURKISH_SUFFIXES = [
|
|
|
432
428
|
"u",
|
|
433
429
|
"\xFC"
|
|
434
430
|
];
|
|
431
|
+
|
|
432
|
+
// src/morphology.ts
|
|
433
|
+
var TURKISH_VOWELS = "ae\u0131io\xF6u\xFC";
|
|
434
|
+
function isVowel(ch) {
|
|
435
|
+
return TURKISH_VOWELS.includes(ch);
|
|
436
|
+
}
|
|
435
437
|
function restoreConsonantSoftening(stem) {
|
|
436
438
|
if (stem.length < 2)
|
|
437
439
|
return [];
|
|
@@ -585,12 +587,7 @@ function getStemCandidates(word, minStemLength = 2, maxDepth = 4) {
|
|
|
585
587
|
return [...new Set(candidatesWithWeight.map((c) => c.candidate))];
|
|
586
588
|
}
|
|
587
589
|
|
|
588
|
-
// src/
|
|
589
|
-
import * as fs from "fs";
|
|
590
|
-
import * as path from "path";
|
|
591
|
-
import * as os from "os";
|
|
592
|
-
import * as https from "https";
|
|
593
|
-
import * as tls from "tls";
|
|
590
|
+
// src/data/misspellings.ts
|
|
594
591
|
var COMMON_MISSPELLINGS = {
|
|
595
592
|
// -şey ile biten ve ayrı yazılması zorunlu söz öbekleri
|
|
596
593
|
her\u015Fey: "her \u015Fey",
|
|
@@ -744,75 +741,10 @@ var COMMON_MISSPELLINGS = {
|
|
|
744
741
|
babaanne: "babaanne"
|
|
745
742
|
};
|
|
746
743
|
var SEY_EXCEPTIONS = /* @__PURE__ */ new Set(["d\xFC\u015Fey", "e\u015Fey", "konsey", "jersey", "\u015Fey"]);
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
];
|
|
752
|
-
var KEYBOARD_COORDS = (() => {
|
|
753
|
-
const coords = {};
|
|
754
|
-
KEYBOARD_ROWS.forEach(([keys, offset], row) => {
|
|
755
|
-
[...keys].forEach((key, col) => {
|
|
756
|
-
coords[key] = [col + offset, row];
|
|
757
|
-
});
|
|
758
|
-
});
|
|
759
|
-
return coords;
|
|
760
|
-
})();
|
|
761
|
-
var DIACRITIC_SIBLINGS = {
|
|
762
|
-
\u0131: "i",
|
|
763
|
-
i: "\u0131",
|
|
764
|
-
\u00F6: "o",
|
|
765
|
-
o: "\xF6",
|
|
766
|
-
\u00FC: "u",
|
|
767
|
-
u: "\xFC",
|
|
768
|
-
\u015F: "s",
|
|
769
|
-
s: "\u015F",
|
|
770
|
-
\u00E7: "c",
|
|
771
|
-
c: "\xE7",
|
|
772
|
-
\u011F: "g",
|
|
773
|
-
g: "\u011F",
|
|
774
|
-
\u00E2: "a",
|
|
775
|
-
a: "\xE2"
|
|
776
|
-
};
|
|
777
|
-
var KEYBOARD_ROW_SUB_COST = 0.4;
|
|
778
|
-
var KEYBOARD_DIAGONAL_SUB_COST = 0.55;
|
|
779
|
-
var DIACRITIC_SUB_COST = 0.3;
|
|
780
|
-
var TRANSPOSITION_COST = 0.8;
|
|
781
|
-
function keyboardSubCost(a, b) {
|
|
782
|
-
if (a === b)
|
|
783
|
-
return 0;
|
|
784
|
-
if (DIACRITIC_SIBLINGS[a] === b)
|
|
785
|
-
return DIACRITIC_SUB_COST;
|
|
786
|
-
const pa = KEYBOARD_COORDS[a];
|
|
787
|
-
const pb = KEYBOARD_COORDS[b];
|
|
788
|
-
if (!pa || !pb)
|
|
789
|
-
return 1;
|
|
790
|
-
const dx = Math.abs(pa[0] - pb[0]);
|
|
791
|
-
const dy = Math.abs(pa[1] - pb[1]);
|
|
792
|
-
if (dy === 0 && dx <= 1 + 1e-9)
|
|
793
|
-
return KEYBOARD_ROW_SUB_COST;
|
|
794
|
-
if (dy === 1 && dx <= 1 + 1e-9)
|
|
795
|
-
return KEYBOARD_DIAGONAL_SUB_COST;
|
|
796
|
-
return 1;
|
|
797
|
-
}
|
|
798
|
-
var TDK = class {
|
|
799
|
-
static BASE_URL = "https://sozluk.gov.tr";
|
|
800
|
-
static AUDIO_API_HOST = "api.sozluk.gov.tr";
|
|
801
|
-
static KUBBEALTI_HOST = "eski.lugatim.com";
|
|
802
|
-
/**
|
|
803
|
-
* `eski.lugatim.com` (Kubbealtı Lugatı's data API) sends only its leaf
|
|
804
|
-
* certificate during the TLS handshake, omitting the intermediates a
|
|
805
|
-
* correctly configured server would include — a server-side misconfiguration,
|
|
806
|
-
* not something we should paper over by disabling verification. These are
|
|
807
|
-
* the two certificates the server *should* be sending (fetched from the
|
|
808
|
-
* leaf's own Authority Information Access URLs), supplied here so Node can
|
|
809
|
-
* still build a full, properly verified chain up to a root it already
|
|
810
|
-
* trusts (ISRG Root X1). If Let's Encrypt rotates this intermediate, this
|
|
811
|
-
* stops working and every Kubbealtı call fails closed to `null` — same
|
|
812
|
-
* fail-closed contract as the rest of this file's fragile integrations.
|
|
813
|
-
*/
|
|
814
|
-
static KUBBEALTI_EXTRA_CA = [
|
|
815
|
-
`-----BEGIN CERTIFICATE-----
|
|
744
|
+
|
|
745
|
+
// src/data/kubbealti-ca.ts
|
|
746
|
+
var KUBBEALTI_EXTRA_CA = [
|
|
747
|
+
`-----BEGIN CERTIFICATE-----
|
|
816
748
|
MIIE2jCCAsKgAwIBAgIQTr0klH4k05SALYSlL9WzGTANBgkqhkiG9w0BAQsFADAu
|
|
817
749
|
MQswCQYDVQQGEwJVUzENMAsGA1UEChMESVNSRzEQMA4GA1UEAxMHUm9vdCBZUjAe
|
|
818
750
|
Fw0yNTA5MDMwMDAwMDBaFw0yODA5MDIyMzU5NTlaMDMxCzAJBgNVBAYTAlVTMRYw
|
|
@@ -840,7 +772,7 @@ NA3wJdl4DDUuQSV8hBgx6zoI1ZSGORprDFux7c6rhc77QZMSRrEgomBeklervEve
|
|
|
840
772
|
86ylWmZ3WWHV6RLMi8xNvjd71r4EPIGgY7BZU/VPBkq+uA7Gb6mbJnFgV43uh3xy
|
|
841
773
|
LRFgxIAphIukwTGSMZZR+AI+Qnp0BYTWovHXozOf3H8r6hozEoT02JHn0AeTfA==
|
|
842
774
|
-----END CERTIFICATE-----`,
|
|
843
|
-
|
|
775
|
+
`-----BEGIN CERTIFICATE-----
|
|
844
776
|
MIIF9DCCA9ygAwIBAgIRAPJLbRf52a18scn+p4eCaZ8wDQYJKoZIhvcNAQELBQAw
|
|
845
777
|
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
|
846
778
|
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjYwNTEzMDAwMDAw
|
|
@@ -874,7 +806,112 @@ RGMuHGnzS3hFIrRTfKxrzUZ9RzQWzEG3K6fJ3r2nqSltkeytis9DIBoFY9VmVyjL
|
|
|
874
806
|
M71DMi+y1+TRSJVClEMwvA4yL++7q9XZx5r5wBRWB4kQTKH5qyoZnDw7iiuh1lID
|
|
875
807
|
yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
876
808
|
-----END CERTIFICATE-----`
|
|
877
|
-
|
|
809
|
+
];
|
|
810
|
+
|
|
811
|
+
// src/data/keyboard-layout.ts
|
|
812
|
+
var KEYBOARD_ROWS = [
|
|
813
|
+
["qwertyu\u0131op\u011F\xFC", 0],
|
|
814
|
+
["asdfghjkl\u015Fi", 0.5],
|
|
815
|
+
["zxcvbnm\xF6\xE7", 1]
|
|
816
|
+
];
|
|
817
|
+
var KEYBOARD_COORDS = (() => {
|
|
818
|
+
const coords = {};
|
|
819
|
+
KEYBOARD_ROWS.forEach(([keys, offset], row) => {
|
|
820
|
+
[...keys].forEach((key, col) => {
|
|
821
|
+
coords[key] = [col + offset, row];
|
|
822
|
+
});
|
|
823
|
+
});
|
|
824
|
+
return coords;
|
|
825
|
+
})();
|
|
826
|
+
var DIACRITIC_SIBLINGS = {
|
|
827
|
+
\u0131: "i",
|
|
828
|
+
i: "\u0131",
|
|
829
|
+
\u00F6: "o",
|
|
830
|
+
o: "\xF6",
|
|
831
|
+
\u00FC: "u",
|
|
832
|
+
u: "\xFC",
|
|
833
|
+
\u015F: "s",
|
|
834
|
+
s: "\u015F",
|
|
835
|
+
\u00E7: "c",
|
|
836
|
+
c: "\xE7",
|
|
837
|
+
\u011F: "g",
|
|
838
|
+
g: "\u011F",
|
|
839
|
+
\u00E2: "a",
|
|
840
|
+
a: "\xE2"
|
|
841
|
+
};
|
|
842
|
+
var KEYBOARD_ROW_SUB_COST = 0.4;
|
|
843
|
+
var KEYBOARD_DIAGONAL_SUB_COST = 0.55;
|
|
844
|
+
var DIACRITIC_SUB_COST = 0.3;
|
|
845
|
+
var TRANSPOSITION_COST = 0.8;
|
|
846
|
+
function keyboardSubCost(a, b) {
|
|
847
|
+
if (a === b)
|
|
848
|
+
return 0;
|
|
849
|
+
if (DIACRITIC_SIBLINGS[a] === b)
|
|
850
|
+
return DIACRITIC_SUB_COST;
|
|
851
|
+
const pa = KEYBOARD_COORDS[a];
|
|
852
|
+
const pb = KEYBOARD_COORDS[b];
|
|
853
|
+
if (!pa || !pb)
|
|
854
|
+
return 1;
|
|
855
|
+
const dx = Math.abs(pa[0] - pb[0]);
|
|
856
|
+
const dy = Math.abs(pa[1] - pb[1]);
|
|
857
|
+
if (dy === 0 && dx <= 1 + 1e-9)
|
|
858
|
+
return KEYBOARD_ROW_SUB_COST;
|
|
859
|
+
if (dy === 1 && dx <= 1 + 1e-9)
|
|
860
|
+
return KEYBOARD_DIAGONAL_SUB_COST;
|
|
861
|
+
return 1;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// src/lib/edit-distance.ts
|
|
865
|
+
function damerauLevenshtein(a, b) {
|
|
866
|
+
const dp = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
|
|
867
|
+
for (let i = 0; i <= a.length; i++)
|
|
868
|
+
dp[i][0] = i;
|
|
869
|
+
for (let j = 0; j <= b.length; j++)
|
|
870
|
+
dp[0][j] = j;
|
|
871
|
+
for (let i = 1; i <= a.length; i++) {
|
|
872
|
+
for (let j = 1; j <= b.length; j++) {
|
|
873
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
874
|
+
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
|
|
875
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
876
|
+
dp[i][j] = Math.min(dp[i][j], dp[i - 2][j - 2] + cost);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
return dp[a.length][b.length];
|
|
881
|
+
}
|
|
882
|
+
function keyboardAwareDistance(a, b) {
|
|
883
|
+
const dp = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
|
|
884
|
+
for (let i = 0; i <= a.length; i++)
|
|
885
|
+
dp[i][0] = i;
|
|
886
|
+
for (let j = 0; j <= b.length; j++)
|
|
887
|
+
dp[0][j] = j;
|
|
888
|
+
for (let i = 1; i <= a.length; i++) {
|
|
889
|
+
for (let j = 1; j <= b.length; j++) {
|
|
890
|
+
const cost = keyboardSubCost(a[i - 1], b[j - 1]);
|
|
891
|
+
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
|
|
892
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
893
|
+
dp[i][j] = Math.min(dp[i][j], dp[i - 2][j - 2] + TRANSPOSITION_COST);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return dp[a.length][b.length];
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// src/lib/html.ts
|
|
901
|
+
function htmlToPlainText(html) {
|
|
902
|
+
return html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div)>/gi, "\n\n").replace(/<[^>]+>/g, "").replace(/ /gi, " ").replace(/</gi, "<").replace(/>/gi, ">").replace(/"/gi, '"').replace(/'|’/gi, "'").replace(/&/gi, "&").replace(/[ \t]+/g, " ").replace(/[ \t]*\n[ \t]*/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// src/tdk.ts
|
|
906
|
+
import * as fs from "fs";
|
|
907
|
+
import * as path from "path";
|
|
908
|
+
import * as os from "os";
|
|
909
|
+
import * as https from "https";
|
|
910
|
+
import * as tls from "tls";
|
|
911
|
+
var TDK = class {
|
|
912
|
+
static BASE_URL = "https://sozluk.gov.tr";
|
|
913
|
+
static AUDIO_API_HOST = "api.sozluk.gov.tr";
|
|
914
|
+
static KUBBEALTI_HOST = "eski.lugatim.com";
|
|
878
915
|
// Configuration
|
|
879
916
|
static defaultTimeoutMs = 8e3;
|
|
880
917
|
static defaultRetries = 1;
|
|
@@ -1421,12 +1458,12 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1421
1458
|
continue;
|
|
1422
1459
|
if (Math.abs(candidate.length - cleanWord.length) > 2)
|
|
1423
1460
|
continue;
|
|
1424
|
-
const rawDist =
|
|
1461
|
+
const rawDist = damerauLevenshtein(cleanWord, candidate);
|
|
1425
1462
|
if (rawDist === 0 || rawDist > 2)
|
|
1426
1463
|
continue;
|
|
1427
1464
|
const firstMismatch = candidate[0] === cleanWord[0] ? 0 : 1;
|
|
1428
1465
|
const lengthMismatch = candidate.length === cleanWord.length ? 0 : 1;
|
|
1429
|
-
const score =
|
|
1466
|
+
const score = keyboardAwareDistance(cleanWord, candidate) + (firstMismatch > 0 ? 1.2 : 0);
|
|
1430
1467
|
const better = !best || score < best.score - 1e-9 || Math.abs(score - best.score) < 1e-9 && firstMismatch < best.firstMismatch || Math.abs(score - best.score) < 1e-9 && firstMismatch === best.firstMismatch && lengthMismatch < best.lengthMismatch;
|
|
1431
1468
|
if (better) {
|
|
1432
1469
|
best = { candidate, score, rawDist, firstMismatch, lengthMismatch };
|
|
@@ -1557,14 +1594,11 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1557
1594
|
const contentEnd = html.indexOf("<footer", contentStart);
|
|
1558
1595
|
if (contentEnd === -1)
|
|
1559
1596
|
return null;
|
|
1560
|
-
return
|
|
1597
|
+
return htmlToPlainText(html.slice(contentStart, contentEnd));
|
|
1561
1598
|
} catch {
|
|
1562
1599
|
return null;
|
|
1563
1600
|
}
|
|
1564
1601
|
}
|
|
1565
|
-
static htmlToPlainText(html) {
|
|
1566
|
-
return html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div)>/gi, "\n\n").replace(/<[^>]+>/g, "").replace(/ /gi, " ").replace(/</gi, "<").replace(/>/gi, ">").replace(/"/gi, '"').replace(/'|’/gi, "'").replace(/&/gi, "&").replace(/[ \t]+/g, " ").replace(/[ \t]*\n[ \t]*/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
1567
|
-
}
|
|
1568
1602
|
/**
|
|
1569
1603
|
* GETs a JSON path from Kubbealtı Lugatı's data API (`eski.lugatim.com`),
|
|
1570
1604
|
* supplying `KUBBEALTI_EXTRA_CA` to work around that host's incomplete
|
|
@@ -1578,7 +1612,7 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1578
1612
|
hostname: this.KUBBEALTI_HOST,
|
|
1579
1613
|
path: path2,
|
|
1580
1614
|
method: "GET",
|
|
1581
|
-
ca: [...tls.rootCertificates, ...
|
|
1615
|
+
ca: [...tls.rootCertificates, ...KUBBEALTI_EXTRA_CA],
|
|
1582
1616
|
headers: {
|
|
1583
1617
|
"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"
|
|
1584
1618
|
}
|
|
@@ -1670,7 +1704,7 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1670
1704
|
const entries = await this.getKubbealti(word);
|
|
1671
1705
|
if (!entries)
|
|
1672
1706
|
return null;
|
|
1673
|
-
return entries.map((e) =>
|
|
1707
|
+
return entries.map((e) => htmlToPlainText(e.anlam));
|
|
1674
1708
|
}
|
|
1675
1709
|
/**
|
|
1676
1710
|
* Autocomplete suggestions from Kubbealtı Lugatı's own typeahead endpoint
|
|
@@ -1706,7 +1740,7 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1706
1740
|
const match = html.match(/<meta name="description" content="([^"]*)"/);
|
|
1707
1741
|
if (!match)
|
|
1708
1742
|
return null;
|
|
1709
|
-
const description =
|
|
1743
|
+
const description = htmlToPlainText(match[1]);
|
|
1710
1744
|
if (description === "\xC7a\u011Fda\u015F T\xFCrk\xE7enin Etimolojisi")
|
|
1711
1745
|
return null;
|
|
1712
1746
|
return description;
|
|
@@ -1937,56 +1971,6 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1937
1971
|
}
|
|
1938
1972
|
return analyses;
|
|
1939
1973
|
}
|
|
1940
|
-
/**
|
|
1941
|
-
* Damerau-Levenshtein edit-distance (optimal string alignment variant):
|
|
1942
|
-
* like classic Levenshtein but also counts an adjacent-character
|
|
1943
|
-
* transposition (e.g. "yanlız" -> "yalnız") as a single edit instead of
|
|
1944
|
-
* two substitutions — a very common class of typo that plain Levenshtein
|
|
1945
|
-
* otherwise misses.
|
|
1946
|
-
*/
|
|
1947
|
-
static damerauLevenshtein(a, b) {
|
|
1948
|
-
const dp = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
|
|
1949
|
-
for (let i = 0; i <= a.length; i++)
|
|
1950
|
-
dp[i][0] = i;
|
|
1951
|
-
for (let j = 0; j <= b.length; j++)
|
|
1952
|
-
dp[0][j] = j;
|
|
1953
|
-
for (let i = 1; i <= a.length; i++) {
|
|
1954
|
-
for (let j = 1; j <= b.length; j++) {
|
|
1955
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
1956
|
-
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
|
|
1957
|
-
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
1958
|
-
dp[i][j] = Math.min(dp[i][j], dp[i - 2][j - 2] + cost);
|
|
1959
|
-
}
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
return dp[a.length][b.length];
|
|
1963
|
-
}
|
|
1964
|
-
/**
|
|
1965
|
-
* Keyboard- and diacritic-aware edit distance: same optimal-string-alignment
|
|
1966
|
-
* recurrence as {@link damerauLevenshtein}, but a substitution is charged by
|
|
1967
|
-
* {@link keyboardSubCost} (a fraction of an edit when the two letters are
|
|
1968
|
-
* adjacent on a Turkish Q keyboard or are ASCII/diacritic siblings) and a
|
|
1969
|
-
* transposition costs {@link TRANSPOSITION_COST}. Insertions and deletions
|
|
1970
|
-
* still cost a full 1. Used only to *rank* spelling candidates; the plain
|
|
1971
|
-
* integer distance still gates whether a suggestion is offered at all.
|
|
1972
|
-
*/
|
|
1973
|
-
static keyboardAwareDistance(a, b) {
|
|
1974
|
-
const dp = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
|
|
1975
|
-
for (let i = 0; i <= a.length; i++)
|
|
1976
|
-
dp[i][0] = i;
|
|
1977
|
-
for (let j = 0; j <= b.length; j++)
|
|
1978
|
-
dp[0][j] = j;
|
|
1979
|
-
for (let i = 1; i <= a.length; i++) {
|
|
1980
|
-
for (let j = 1; j <= b.length; j++) {
|
|
1981
|
-
const cost = keyboardSubCost(a[i - 1], b[j - 1]);
|
|
1982
|
-
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
|
|
1983
|
-
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
1984
|
-
dp[i][j] = Math.min(dp[i][j], dp[i - 2][j - 2] + TRANSPOSITION_COST);
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
}
|
|
1988
|
-
return dp[a.length][b.length];
|
|
1989
|
-
}
|
|
1990
1974
|
/**
|
|
1991
1975
|
* Fetches multiple words concurrently with a small delay to avoid rate limiting.
|
|
1992
1976
|
*/
|
|
@@ -2460,9 +2444,9 @@ export {
|
|
|
2460
2444
|
TDKError,
|
|
2461
2445
|
TDKValidationError,
|
|
2462
2446
|
TDKNetworkError,
|
|
2447
|
+
TURKISH_SUFFIXES,
|
|
2463
2448
|
TURKISH_VOWELS,
|
|
2464
2449
|
isVowel,
|
|
2465
|
-
TURKISH_SUFFIXES,
|
|
2466
2450
|
restoreConsonantSoftening,
|
|
2467
2451
|
restoreVowelDrop,
|
|
2468
2452
|
restoreGemination,
|