tdk-api-wrapper 1.2.2 → 1.3.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/README.md +11 -0
- package/dist/{chunk-SNY3KUCF.mjs → chunk-ACMGCL7T.mjs} +294 -1
- package/dist/cli.js +339 -3
- package/dist/cli.mjs +46 -3
- package/dist/index.d.mts +96 -1
- package/dist/index.d.ts +96 -1
- package/dist/index.js +294 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/src/cli.ts +41 -1
- package/src/tdk.ts +301 -1
- package/src/types.ts +10 -0
package/dist/cli.js
CHANGED
|
@@ -55,9 +55,87 @@ var fs = __toESM(require("fs"));
|
|
|
55
55
|
var path = __toESM(require("path"));
|
|
56
56
|
var os = __toESM(require("os"));
|
|
57
57
|
var https = __toESM(require("https"));
|
|
58
|
+
var tls = __toESM(require("tls"));
|
|
58
59
|
var TDK = class {
|
|
59
60
|
static BASE_URL = "https://sozluk.gov.tr";
|
|
60
61
|
static AUDIO_API_HOST = "api.sozluk.gov.tr";
|
|
62
|
+
static KUBBEALTI_HOST = "eski.lugatim.com";
|
|
63
|
+
/**
|
|
64
|
+
* `eski.lugatim.com` (Kubbealtı Lugatı's data API) sends only its leaf
|
|
65
|
+
* certificate during the TLS handshake, omitting the intermediates a
|
|
66
|
+
* correctly configured server would include — a server-side misconfiguration,
|
|
67
|
+
* not something we should paper over by disabling verification. These are
|
|
68
|
+
* the two certificates the server *should* be sending (fetched from the
|
|
69
|
+
* leaf's own Authority Information Access URLs), supplied here so Node can
|
|
70
|
+
* still build a full, properly verified chain up to a root it already
|
|
71
|
+
* trusts (ISRG Root X1). If Let's Encrypt rotates this intermediate, this
|
|
72
|
+
* stops working and every Kubbealtı call fails closed to `null` — same
|
|
73
|
+
* fail-closed contract as the rest of this file's fragile integrations.
|
|
74
|
+
*/
|
|
75
|
+
static KUBBEALTI_EXTRA_CA = [
|
|
76
|
+
`-----BEGIN CERTIFICATE-----
|
|
77
|
+
MIIE2jCCAsKgAwIBAgIQTr0klH4k05SALYSlL9WzGTANBgkqhkiG9w0BAQsFADAu
|
|
78
|
+
MQswCQYDVQQGEwJVUzENMAsGA1UEChMESVNSRzEQMA4GA1UEAxMHUm9vdCBZUjAe
|
|
79
|
+
Fw0yNTA5MDMwMDAwMDBaFw0yODA5MDIyMzU5NTlaMDMxCzAJBgNVBAYTAlVTMRYw
|
|
80
|
+
FAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQDEwNZUjIwggEiMA0GCSqGSIb3
|
|
81
|
+
DQEBAQUAA4IBDwAwggEKAoIBAQDZ0LxwBppqh84luqMerV/eeL/fXQ7mLQQv1Lnp
|
|
82
|
+
WKZbyvGpx6wh6AfnslAnF6ewTkcHA+gSOoBvm3Dfm06AuGiF+KRut4fAcowqnAQQ
|
|
83
|
+
CW98+QPP/eOv/wug7Iyk4NkOxf2I6g2f55T6nJoOTLFcukeRq80JGQEYan+dPFr9
|
|
84
|
+
OGUgQK2hGKgNkW87pappsOAuUJcroYhRt5uUis4qaZireiseu32gzDJNBAiKtsvd
|
|
85
|
+
6HX4v25bpkRNcS/B/Gtc9kVbUpD+2PLPxdei3Tim55k4tfAEXwD2qyiPTxrTNq6l
|
|
86
|
+
N+AMr5g2c1dNqkOTwjxeV6L5lpP1rGiYvLnRaPlOqyZRPW+5AgMBAAGjge4wgesw
|
|
87
|
+
DgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBIGA1UdEwEB/wQI
|
|
88
|
+
MAYBAf8CAQAwHQYDVR0OBBYEFEAVLSZ57TIgnt+ach3WMh+BDIEMMB8GA1UdIwQY
|
|
89
|
+
MBaAFN7nW2DQIm1AKH0/DQH+pLVStFGUMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEF
|
|
90
|
+
BQcwAoYWaHR0cDovL3lyLmkubGVuY3Iub3JnLzATBgNVHSAEDDAKMAgGBmeBDAEC
|
|
91
|
+
ATAnBgNVHR8EIDAeMBygGqAYhhZodHRwOi8veXIuYy5sZW5jci5vcmcvMA0GCSqG
|
|
92
|
+
SIb3DQEBCwUAA4ICAQB0ZUQWZ9/Yn9COEpo+JfecMnB0h0vwDm/M66IqXqw3LoaL
|
|
93
|
+
mx9lZvRTeDIS67PUeI3yCA2W6PKRD0/FE/G57lOmS+Xy5AaaL00ICGOqjNcCaMWW
|
|
94
|
+
8o8nevHOd4i4lqgtznE/28QwlcdJyF8yBiWHpnyjhEpmNWJURgOCOg2xpwRMBCsj
|
|
95
|
+
MScqYPtOhBeuYQvSwAEeTML2Ukh6uGuX4E14q65Ja8cdjF5bAldnP1eE4FBaAwsZ
|
|
96
|
+
G2fOqqrKV03Y85Nw2btedP1AtliQuJZs/Jo/gXxXdc7LrH3McgnpnbTiAncX7yES
|
|
97
|
+
hP6kzQejllqMCIt52HOjxDGWafS7Xw+DKwqmH+Eqy8dcbOuag/1AYlQoKNVK3F5q
|
|
98
|
+
Hh6tEDiMqQcLIibGKteE6iHo4A/bIScbzrhXUYuism42ZYzmc48FMVIH3qy4L84E
|
|
99
|
+
TdAH2gtxw0PAhvRVXp8HP7wfngpzsN/8xOTpeRSbM4+Qbc56G6+Bifmv6sk1ieQb
|
|
100
|
+
NA3wJdl4DDUuQSV8hBgx6zoI1ZSGORprDFux7c6rhc77QZMSRrEgomBeklervEve
|
|
101
|
+
86ylWmZ3WWHV6RLMi8xNvjd71r4EPIGgY7BZU/VPBkq+uA7Gb6mbJnFgV43uh3xy
|
|
102
|
+
LRFgxIAphIukwTGSMZZR+AI+Qnp0BYTWovHXozOf3H8r6hozEoT02JHn0AeTfA==
|
|
103
|
+
-----END CERTIFICATE-----`,
|
|
104
|
+
`-----BEGIN CERTIFICATE-----
|
|
105
|
+
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
|
|
137
|
+
-----END CERTIFICATE-----`
|
|
138
|
+
];
|
|
61
139
|
// Cache Mechanism
|
|
62
140
|
static isCacheEnabled = false;
|
|
63
141
|
static wordCache = /* @__PURE__ */ new Map();
|
|
@@ -567,7 +645,222 @@ var TDK = class {
|
|
|
567
645
|
}
|
|
568
646
|
}
|
|
569
647
|
static htmlToPlainText(html) {
|
|
570
|
-
return html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div)>/gi, "\n\n").replace(/<[^>]+>/g, "").replace(/ /gi, " ").replace(/&
|
|
648
|
+
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();
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* GETs a JSON path from Kubbealtı Lugatı's data API (`eski.lugatim.com`),
|
|
652
|
+
* supplying `KUBBEALTI_EXTRA_CA` to work around that host's incomplete
|
|
653
|
+
* certificate chain (see the constant's doc comment). Fails closed to
|
|
654
|
+
* `null` on any error — network, TLS, HTTP, or JSON parse.
|
|
655
|
+
*/
|
|
656
|
+
static fetchKubbealtiJson(path2) {
|
|
657
|
+
return new Promise((resolve) => {
|
|
658
|
+
const req = https.request(
|
|
659
|
+
{
|
|
660
|
+
hostname: this.KUBBEALTI_HOST,
|
|
661
|
+
path: path2,
|
|
662
|
+
method: "GET",
|
|
663
|
+
ca: [...tls.rootCertificates, ...this.KUBBEALTI_EXTRA_CA],
|
|
664
|
+
headers: {
|
|
665
|
+
"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"
|
|
666
|
+
}
|
|
667
|
+
},
|
|
668
|
+
(res) => {
|
|
669
|
+
if (res.statusCode !== 200) {
|
|
670
|
+
res.resume();
|
|
671
|
+
resolve(null);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
let body = "";
|
|
675
|
+
res.on("data", (chunk) => body += chunk);
|
|
676
|
+
res.on("end", () => {
|
|
677
|
+
try {
|
|
678
|
+
resolve(JSON.parse(body));
|
|
679
|
+
} catch {
|
|
680
|
+
resolve(null);
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
);
|
|
685
|
+
req.on("error", () => resolve(null));
|
|
686
|
+
req.end();
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Kubbealtı indexes headwords with full classical Turkish orthography,
|
|
691
|
+
* including letters that a plain-ASCII-ish query tends to drop — most
|
|
692
|
+
* commonly ü/ö/ç/ğ/ş, but also the circumflex ("düzeltme işareti") used in
|
|
693
|
+
* Arabic/Persian loanwords like "rüzgâr". A search for "ruzgar" misses
|
|
694
|
+
* entirely (verified: even "ruzgâr" alone still misses — it's the missing
|
|
695
|
+
* ü, not the missing â, that actually breaks the match). This generates
|
|
696
|
+
* single-letter-substitution variants to retry, one substitution per
|
|
697
|
+
* variant (not combinatorial) — covers the overwhelmingly common case of
|
|
698
|
+
* one "de-Turkished" letter without an explosion of API calls for words
|
|
699
|
+
* with several.
|
|
700
|
+
*/
|
|
701
|
+
static TURKISH_DEASCII_MAP = {
|
|
702
|
+
a: ["\xE2"],
|
|
703
|
+
i: ["\u0131", "\xEE"],
|
|
704
|
+
o: ["\xF6"],
|
|
705
|
+
u: ["\xFC", "\xFB"],
|
|
706
|
+
c: ["\xE7"],
|
|
707
|
+
g: ["\u011F"],
|
|
708
|
+
s: ["\u015F"]
|
|
709
|
+
};
|
|
710
|
+
static generateTurkishVariants(word2) {
|
|
711
|
+
const lower = word2.trim().toLocaleLowerCase("tr-TR");
|
|
712
|
+
const variants = [];
|
|
713
|
+
for (let i = 0; i < lower.length; i++) {
|
|
714
|
+
for (const replacement of this.TURKISH_DEASCII_MAP[lower[i]] ?? []) {
|
|
715
|
+
variants.push(lower.slice(0, i) + replacement + lower.slice(i + 1));
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return variants;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Returns Kubbealtı Lugatı ("Misalli Büyük Türkçe Sözlük") entries for a
|
|
722
|
+
* word, scraped from the site's own data API — undocumented, and Kubbealtı
|
|
723
|
+
* Lugatı is a commercial dictionary product, unlike TDK's or Wiktionary's
|
|
724
|
+
* openly-published data, so use this in line with their terms. `anlam` is
|
|
725
|
+
* raw HTML (rich typography markup); use `getKubbealtiMeanings()` for
|
|
726
|
+
* plain text. Falls back to `generateTurkishVariants()` if the exact query
|
|
727
|
+
* comes up empty (see its doc comment). Returns `null` on any fetch/parse
|
|
728
|
+
* failure, `[]` if no variant matches either.
|
|
729
|
+
*/
|
|
730
|
+
static async getKubbealti(word2) {
|
|
731
|
+
if (!word2 || word2.trim() === "")
|
|
732
|
+
return null;
|
|
733
|
+
const data = await this.fetchKubbealtiJson(`/rest/s/${encodeURIComponent(word2.trim())}/`);
|
|
734
|
+
if (!data || !Array.isArray(data.content))
|
|
735
|
+
return null;
|
|
736
|
+
if (data.content.length > 0) {
|
|
737
|
+
return data.content.map((entry) => ({ kelime: entry.kelime, anlam: entry.anlam }));
|
|
738
|
+
}
|
|
739
|
+
for (const variant of this.generateTurkishVariants(word2)) {
|
|
740
|
+
const variantData = await this.fetchKubbealtiJson(`/rest/s/${encodeURIComponent(variant)}/`);
|
|
741
|
+
if (variantData && Array.isArray(variantData.content) && variantData.content.length > 0) {
|
|
742
|
+
return variantData.content.map((entry) => ({ kelime: entry.kelime, anlam: entry.anlam }));
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return [];
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Same as `getKubbealti()` but with each entry's `anlam` HTML stripped to
|
|
749
|
+
* plain text via `htmlToPlainText()`.
|
|
750
|
+
*/
|
|
751
|
+
static async getKubbealtiMeanings(word2) {
|
|
752
|
+
const entries = await this.getKubbealti(word2);
|
|
753
|
+
if (!entries)
|
|
754
|
+
return null;
|
|
755
|
+
return entries.map((e) => this.htmlToPlainText(e.anlam));
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Autocomplete suggestions from Kubbealtı Lugatı's own typeahead endpoint
|
|
759
|
+
* (separate from `getSuggestions()`, which uses TDK's data).
|
|
760
|
+
*/
|
|
761
|
+
static async getKubbealtiSuggestions(prefix) {
|
|
762
|
+
if (!prefix || prefix.trim() === "")
|
|
763
|
+
return [];
|
|
764
|
+
const data = await this.fetchKubbealtiJson(`/rest/word-search/${encodeURIComponent(prefix.trim())}`);
|
|
765
|
+
if (!Array.isArray(data))
|
|
766
|
+
return [];
|
|
767
|
+
return data.map((item) => item.display).filter(Boolean);
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Returns the etymology paragraph for a word from Nişanyan Sözlük, scraped
|
|
771
|
+
* from that page's server-rendered `<meta name="description">` tag (the
|
|
772
|
+
* page already puts the full etymology text there for SEO, so no need to
|
|
773
|
+
* parse the site's internal SvelteKit data format). Returns `null` if the
|
|
774
|
+
* word isn't found (the page falls back to a generic site tagline in that
|
|
775
|
+
* case) or the request fails.
|
|
776
|
+
*/
|
|
777
|
+
static async getNisanyan(word2) {
|
|
778
|
+
if (!word2 || word2.trim() === "")
|
|
779
|
+
return null;
|
|
780
|
+
try {
|
|
781
|
+
const response = await fetch(
|
|
782
|
+
`https://www.nisanyansozluk.com/kelime/${encodeURIComponent(word2.trim().toLocaleLowerCase("tr-TR"))}`,
|
|
783
|
+
{ headers: { "User-Agent": "TDK-API-Nodejs-Wrapper/1.0" } }
|
|
784
|
+
);
|
|
785
|
+
if (!response.ok)
|
|
786
|
+
return null;
|
|
787
|
+
const html = await response.text();
|
|
788
|
+
const match = html.match(/<meta name="description" content="([^"]*)"/);
|
|
789
|
+
if (!match)
|
|
790
|
+
return null;
|
|
791
|
+
const description = this.htmlToPlainText(match[1]);
|
|
792
|
+
if (description === "\xC7a\u011Fda\u015F T\xFCrk\xE7enin Etimolojisi")
|
|
793
|
+
return null;
|
|
794
|
+
return description;
|
|
795
|
+
} catch {
|
|
796
|
+
return null;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
static async fetchWiktionaryEntry(title) {
|
|
800
|
+
try {
|
|
801
|
+
const url = `https://tr.wiktionary.org/w/api.php?action=query&prop=extracts&titles=${encodeURIComponent(
|
|
802
|
+
title
|
|
803
|
+
)}&format=json&explaintext=1&formatversion=2`;
|
|
804
|
+
const response = await fetch(url, { headers: { "User-Agent": "TDK-API-Nodejs-Wrapper/1.0" } });
|
|
805
|
+
if (!response.ok)
|
|
806
|
+
return null;
|
|
807
|
+
const data = await response.json();
|
|
808
|
+
const page = data?.query?.pages?.[0];
|
|
809
|
+
if (!page || page.missing || !page.extract)
|
|
810
|
+
return null;
|
|
811
|
+
const raw = page.extract;
|
|
812
|
+
const sections = {};
|
|
813
|
+
const parts = raw.split(/\n(={2,4})\s*(.+?)\s*\1\n/);
|
|
814
|
+
for (let i = 1; i < parts.length; i += 3) {
|
|
815
|
+
const title2 = parts[i + 1]?.trim();
|
|
816
|
+
const content = parts[i + 2]?.trim();
|
|
817
|
+
if (title2)
|
|
818
|
+
sections[title2] = content ?? "";
|
|
819
|
+
}
|
|
820
|
+
return { raw, sections };
|
|
821
|
+
} catch {
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Returns the Turkish Wiktionary (`tr.wiktionary.org`) entry for a word,
|
|
827
|
+
* via MediaWiki's official Action API (`action=query&prop=extracts`) — no
|
|
828
|
+
* scraping involved, this is a stable, documented public API. `sections`
|
|
829
|
+
* splits the plain-text extract on its `== Heading ==`/`=== Heading ===`
|
|
830
|
+
* markers (e.g. "Köken", "Söyleniş", "Ad") for convenience; `raw` has the
|
|
831
|
+
* unsplit text. This wiki has title capitalization turned off
|
|
832
|
+
* ($wgCapitalLinks=false — common for Wiktionaries, since case is
|
|
833
|
+
* meaningful for a dictionary: "Türkiye" the country vs. a lowercase
|
|
834
|
+
* common word), so an exact-case miss retries with the first letter
|
|
835
|
+
* uppercased (Turkish-locale-aware, so "istanbul" tries "İstanbul", not
|
|
836
|
+
* "Istanbul") before giving up. Returns `null` if neither is found or the
|
|
837
|
+
* request fails.
|
|
838
|
+
*/
|
|
839
|
+
static async getWiktionary(word2) {
|
|
840
|
+
if (!word2 || word2.trim() === "")
|
|
841
|
+
return null;
|
|
842
|
+
const trimmed = word2.trim();
|
|
843
|
+
const direct = await this.fetchWiktionaryEntry(trimmed);
|
|
844
|
+
if (direct)
|
|
845
|
+
return direct;
|
|
846
|
+
const capitalized = trimmed.charAt(0).toLocaleUpperCase("tr-TR") + trimmed.slice(1);
|
|
847
|
+
if (capitalized === trimmed)
|
|
848
|
+
return null;
|
|
849
|
+
return this.fetchWiktionaryEntry(capitalized);
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Convenience filter over `getWiktionary()`: returns just one section's
|
|
853
|
+
* text (e.g. `getWiktionarySection(word, "Köken")` for etymology), matched
|
|
854
|
+
* case-insensitively. Returns `null` if the word or the section isn't found.
|
|
855
|
+
*/
|
|
856
|
+
static async getWiktionarySection(word2, sectionName) {
|
|
857
|
+
const entry = await this.getWiktionary(word2);
|
|
858
|
+
if (!entry)
|
|
859
|
+
return null;
|
|
860
|
+
const key = Object.keys(entry.sections).find(
|
|
861
|
+
(k) => k.toLocaleLowerCase("tr-TR") === sectionName.trim().toLocaleLowerCase("tr-TR")
|
|
862
|
+
);
|
|
863
|
+
return key ? entry.sections[key] : null;
|
|
571
864
|
}
|
|
572
865
|
/**
|
|
573
866
|
* Returns compound words that contain this word.
|
|
@@ -818,7 +1111,10 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
818
1111
|
"kural",
|
|
819
1112
|
"karsilastir",
|
|
820
1113
|
"analiz",
|
|
821
|
-
"oneri"
|
|
1114
|
+
"oneri",
|
|
1115
|
+
"kubbealti",
|
|
1116
|
+
"nisanyan",
|
|
1117
|
+
"viki"
|
|
822
1118
|
]);
|
|
823
1119
|
var command = args[0];
|
|
824
1120
|
var word = args.slice(1).join(" ");
|
|
@@ -844,7 +1140,7 @@ async function run() {
|
|
|
844
1140
|
if (!command || command === "--help" || command === "-h") {
|
|
845
1141
|
console.log("Kullan\u0131m: tdk [komut] <kelime> [--json]");
|
|
846
1142
|
console.log(
|
|
847
|
-
"Komutlar: ara, anlam, koken, ornek, hece, uyum, yazim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz, oneri"
|
|
1143
|
+
"Komutlar: ara, anlam, koken, ornek, hece, uyum, yazim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz, oneri, kubbealti, nisanyan, viki"
|
|
848
1144
|
);
|
|
849
1145
|
console.log("Not: Komut belirtilmezse do\u011Frudan kelime anlam\u0131 aran\u0131r (\xF6rn: tdk selam)");
|
|
850
1146
|
process.exit(command ? 0 : 1);
|
|
@@ -1047,6 +1343,46 @@ async function run() {
|
|
|
1047
1343
|
});
|
|
1048
1344
|
break;
|
|
1049
1345
|
}
|
|
1346
|
+
case "kubbealti": {
|
|
1347
|
+
if (!word)
|
|
1348
|
+
throw new Error("Kelime belirtmelisiniz.");
|
|
1349
|
+
const meanings = await TDK.getKubbealtiMeanings(word);
|
|
1350
|
+
printResult(meanings, () => {
|
|
1351
|
+
if (!meanings) {
|
|
1352
|
+
console.log("Kubbealt\u0131 Lugat\u0131'na ula\u015F\u0131lamad\u0131.");
|
|
1353
|
+
} else if (meanings.length === 0) {
|
|
1354
|
+
console.log("Sonu\xE7 bulunamad\u0131.");
|
|
1355
|
+
} else {
|
|
1356
|
+
meanings.forEach((m, i) => console.log(`${i + 1}. ${m}`));
|
|
1357
|
+
}
|
|
1358
|
+
});
|
|
1359
|
+
break;
|
|
1360
|
+
}
|
|
1361
|
+
case "nisanyan": {
|
|
1362
|
+
if (!word)
|
|
1363
|
+
throw new Error("Kelime belirtmelisiniz.");
|
|
1364
|
+
const origin = await TDK.getNisanyan(word);
|
|
1365
|
+
printResult(origin, () => console.log(origin ?? "Sonu\xE7 bulunamad\u0131."));
|
|
1366
|
+
break;
|
|
1367
|
+
}
|
|
1368
|
+
case "viki": {
|
|
1369
|
+
if (!word)
|
|
1370
|
+
throw new Error("Kelime belirtmelisiniz.");
|
|
1371
|
+
const entry = await TDK.getWiktionary(word);
|
|
1372
|
+
printResult(entry, () => {
|
|
1373
|
+
if (!entry) {
|
|
1374
|
+
console.log("Sonu\xE7 bulunamad\u0131.");
|
|
1375
|
+
} else {
|
|
1376
|
+
for (const [title, content] of Object.entries(entry.sections)) {
|
|
1377
|
+
if (content)
|
|
1378
|
+
console.log(`-- ${title} --
|
|
1379
|
+
${content}
|
|
1380
|
+
`);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
break;
|
|
1385
|
+
}
|
|
1050
1386
|
default:
|
|
1051
1387
|
printError("Bilinmeyen komut.");
|
|
1052
1388
|
}
|
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
TDK
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ACMGCL7T.mjs";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
var rawArgs = process.argv.slice(2);
|
|
@@ -24,7 +24,10 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
24
24
|
"kural",
|
|
25
25
|
"karsilastir",
|
|
26
26
|
"analiz",
|
|
27
|
-
"oneri"
|
|
27
|
+
"oneri",
|
|
28
|
+
"kubbealti",
|
|
29
|
+
"nisanyan",
|
|
30
|
+
"viki"
|
|
28
31
|
]);
|
|
29
32
|
var command = args[0];
|
|
30
33
|
var word = args.slice(1).join(" ");
|
|
@@ -50,7 +53,7 @@ async function run() {
|
|
|
50
53
|
if (!command || command === "--help" || command === "-h") {
|
|
51
54
|
console.log("Kullan\u0131m: tdk [komut] <kelime> [--json]");
|
|
52
55
|
console.log(
|
|
53
|
-
"Komutlar: ara, anlam, koken, ornek, hece, uyum, yazim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz, oneri"
|
|
56
|
+
"Komutlar: ara, anlam, koken, ornek, hece, uyum, yazim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz, oneri, kubbealti, nisanyan, viki"
|
|
54
57
|
);
|
|
55
58
|
console.log("Not: Komut belirtilmezse do\u011Frudan kelime anlam\u0131 aran\u0131r (\xF6rn: tdk selam)");
|
|
56
59
|
process.exit(command ? 0 : 1);
|
|
@@ -253,6 +256,46 @@ async function run() {
|
|
|
253
256
|
});
|
|
254
257
|
break;
|
|
255
258
|
}
|
|
259
|
+
case "kubbealti": {
|
|
260
|
+
if (!word)
|
|
261
|
+
throw new Error("Kelime belirtmelisiniz.");
|
|
262
|
+
const meanings = await TDK.getKubbealtiMeanings(word);
|
|
263
|
+
printResult(meanings, () => {
|
|
264
|
+
if (!meanings) {
|
|
265
|
+
console.log("Kubbealt\u0131 Lugat\u0131'na ula\u015F\u0131lamad\u0131.");
|
|
266
|
+
} else if (meanings.length === 0) {
|
|
267
|
+
console.log("Sonu\xE7 bulunamad\u0131.");
|
|
268
|
+
} else {
|
|
269
|
+
meanings.forEach((m, i) => console.log(`${i + 1}. ${m}`));
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
case "nisanyan": {
|
|
275
|
+
if (!word)
|
|
276
|
+
throw new Error("Kelime belirtmelisiniz.");
|
|
277
|
+
const origin = await TDK.getNisanyan(word);
|
|
278
|
+
printResult(origin, () => console.log(origin ?? "Sonu\xE7 bulunamad\u0131."));
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
case "viki": {
|
|
282
|
+
if (!word)
|
|
283
|
+
throw new Error("Kelime belirtmelisiniz.");
|
|
284
|
+
const entry = await TDK.getWiktionary(word);
|
|
285
|
+
printResult(entry, () => {
|
|
286
|
+
if (!entry) {
|
|
287
|
+
console.log("Sonu\xE7 bulunamad\u0131.");
|
|
288
|
+
} else {
|
|
289
|
+
for (const [title, content] of Object.entries(entry.sections)) {
|
|
290
|
+
if (content)
|
|
291
|
+
console.log(`-- ${title} --
|
|
292
|
+
${content}
|
|
293
|
+
`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
256
299
|
default:
|
|
257
300
|
printError("Bilinmeyen komut.");
|
|
258
301
|
}
|
package/dist/index.d.mts
CHANGED
|
@@ -126,6 +126,14 @@ interface WordAnalysis {
|
|
|
126
126
|
meaning: string | null;
|
|
127
127
|
origin: string | null;
|
|
128
128
|
}
|
|
129
|
+
interface KubbealtiEntry {
|
|
130
|
+
kelime: string;
|
|
131
|
+
anlam: string;
|
|
132
|
+
}
|
|
133
|
+
interface WiktionaryEntry {
|
|
134
|
+
raw: string;
|
|
135
|
+
sections: Record<string, string>;
|
|
136
|
+
}
|
|
129
137
|
type TDKResponse = WordInfo[] | {
|
|
130
138
|
error: string;
|
|
131
139
|
};
|
|
@@ -136,6 +144,20 @@ type TDKResponse = WordInfo[] | {
|
|
|
136
144
|
declare class TDK {
|
|
137
145
|
private static readonly BASE_URL;
|
|
138
146
|
private static readonly AUDIO_API_HOST;
|
|
147
|
+
private static readonly KUBBEALTI_HOST;
|
|
148
|
+
/**
|
|
149
|
+
* `eski.lugatim.com` (Kubbealtı Lugatı's data API) sends only its leaf
|
|
150
|
+
* certificate during the TLS handshake, omitting the intermediates a
|
|
151
|
+
* correctly configured server would include — a server-side misconfiguration,
|
|
152
|
+
* not something we should paper over by disabling verification. These are
|
|
153
|
+
* the two certificates the server *should* be sending (fetched from the
|
|
154
|
+
* leaf's own Authority Information Access URLs), supplied here so Node can
|
|
155
|
+
* still build a full, properly verified chain up to a root it already
|
|
156
|
+
* trusts (ISRG Root X1). If Let's Encrypt rotates this intermediate, this
|
|
157
|
+
* stops working and every Kubbealtı call fails closed to `null` — same
|
|
158
|
+
* fail-closed contract as the rest of this file's fragile integrations.
|
|
159
|
+
*/
|
|
160
|
+
private static readonly KUBBEALTI_EXTRA_CA;
|
|
139
161
|
private static isCacheEnabled;
|
|
140
162
|
private static wordCache;
|
|
141
163
|
private static dailyContentCache;
|
|
@@ -296,6 +318,79 @@ declare class TDK {
|
|
|
296
318
|
*/
|
|
297
319
|
private static fetchRuleText;
|
|
298
320
|
private static htmlToPlainText;
|
|
321
|
+
/**
|
|
322
|
+
* GETs a JSON path from Kubbealtı Lugatı's data API (`eski.lugatim.com`),
|
|
323
|
+
* supplying `KUBBEALTI_EXTRA_CA` to work around that host's incomplete
|
|
324
|
+
* certificate chain (see the constant's doc comment). Fails closed to
|
|
325
|
+
* `null` on any error — network, TLS, HTTP, or JSON parse.
|
|
326
|
+
*/
|
|
327
|
+
private static fetchKubbealtiJson;
|
|
328
|
+
/**
|
|
329
|
+
* Kubbealtı indexes headwords with full classical Turkish orthography,
|
|
330
|
+
* including letters that a plain-ASCII-ish query tends to drop — most
|
|
331
|
+
* commonly ü/ö/ç/ğ/ş, but also the circumflex ("düzeltme işareti") used in
|
|
332
|
+
* Arabic/Persian loanwords like "rüzgâr". A search for "ruzgar" misses
|
|
333
|
+
* entirely (verified: even "ruzgâr" alone still misses — it's the missing
|
|
334
|
+
* ü, not the missing â, that actually breaks the match). This generates
|
|
335
|
+
* single-letter-substitution variants to retry, one substitution per
|
|
336
|
+
* variant (not combinatorial) — covers the overwhelmingly common case of
|
|
337
|
+
* one "de-Turkished" letter without an explosion of API calls for words
|
|
338
|
+
* with several.
|
|
339
|
+
*/
|
|
340
|
+
private static readonly TURKISH_DEASCII_MAP;
|
|
341
|
+
private static generateTurkishVariants;
|
|
342
|
+
/**
|
|
343
|
+
* Returns Kubbealtı Lugatı ("Misalli Büyük Türkçe Sözlük") entries for a
|
|
344
|
+
* word, scraped from the site's own data API — undocumented, and Kubbealtı
|
|
345
|
+
* Lugatı is a commercial dictionary product, unlike TDK's or Wiktionary's
|
|
346
|
+
* openly-published data, so use this in line with their terms. `anlam` is
|
|
347
|
+
* raw HTML (rich typography markup); use `getKubbealtiMeanings()` for
|
|
348
|
+
* plain text. Falls back to `generateTurkishVariants()` if the exact query
|
|
349
|
+
* comes up empty (see its doc comment). Returns `null` on any fetch/parse
|
|
350
|
+
* failure, `[]` if no variant matches either.
|
|
351
|
+
*/
|
|
352
|
+
static getKubbealti(word: string): Promise<KubbealtiEntry[] | null>;
|
|
353
|
+
/**
|
|
354
|
+
* Same as `getKubbealti()` but with each entry's `anlam` HTML stripped to
|
|
355
|
+
* plain text via `htmlToPlainText()`.
|
|
356
|
+
*/
|
|
357
|
+
static getKubbealtiMeanings(word: string): Promise<string[] | null>;
|
|
358
|
+
/**
|
|
359
|
+
* Autocomplete suggestions from Kubbealtı Lugatı's own typeahead endpoint
|
|
360
|
+
* (separate from `getSuggestions()`, which uses TDK's data).
|
|
361
|
+
*/
|
|
362
|
+
static getKubbealtiSuggestions(prefix: string): Promise<string[]>;
|
|
363
|
+
/**
|
|
364
|
+
* Returns the etymology paragraph for a word from Nişanyan Sözlük, scraped
|
|
365
|
+
* from that page's server-rendered `<meta name="description">` tag (the
|
|
366
|
+
* page already puts the full etymology text there for SEO, so no need to
|
|
367
|
+
* parse the site's internal SvelteKit data format). Returns `null` if the
|
|
368
|
+
* word isn't found (the page falls back to a generic site tagline in that
|
|
369
|
+
* case) or the request fails.
|
|
370
|
+
*/
|
|
371
|
+
static getNisanyan(word: string): Promise<string | null>;
|
|
372
|
+
private static fetchWiktionaryEntry;
|
|
373
|
+
/**
|
|
374
|
+
* Returns the Turkish Wiktionary (`tr.wiktionary.org`) entry for a word,
|
|
375
|
+
* via MediaWiki's official Action API (`action=query&prop=extracts`) — no
|
|
376
|
+
* scraping involved, this is a stable, documented public API. `sections`
|
|
377
|
+
* splits the plain-text extract on its `== Heading ==`/`=== Heading ===`
|
|
378
|
+
* markers (e.g. "Köken", "Söyleniş", "Ad") for convenience; `raw` has the
|
|
379
|
+
* unsplit text. This wiki has title capitalization turned off
|
|
380
|
+
* ($wgCapitalLinks=false — common for Wiktionaries, since case is
|
|
381
|
+
* meaningful for a dictionary: "Türkiye" the country vs. a lowercase
|
|
382
|
+
* common word), so an exact-case miss retries with the first letter
|
|
383
|
+
* uppercased (Turkish-locale-aware, so "istanbul" tries "İstanbul", not
|
|
384
|
+
* "Istanbul") before giving up. Returns `null` if neither is found or the
|
|
385
|
+
* request fails.
|
|
386
|
+
*/
|
|
387
|
+
static getWiktionary(word: string): Promise<WiktionaryEntry | null>;
|
|
388
|
+
/**
|
|
389
|
+
* Convenience filter over `getWiktionary()`: returns just one section's
|
|
390
|
+
* text (e.g. `getWiktionarySection(word, "Köken")` for etymology), matched
|
|
391
|
+
* case-insensitively. Returns `null` if the word or the section isn't found.
|
|
392
|
+
*/
|
|
393
|
+
static getWiktionarySection(word: string, sectionName: string): Promise<string | null>;
|
|
299
394
|
/**
|
|
300
395
|
* Returns compound words that contain this word.
|
|
301
396
|
*/
|
|
@@ -375,4 +470,4 @@ declare class TDKNetworkError extends TDKError {
|
|
|
375
470
|
});
|
|
376
471
|
}
|
|
377
472
|
|
|
378
|
-
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 };
|
|
473
|
+
export { type Author, type DailyContent, type DailyPick, type Example, type Feature, type KubbealtiEntry, type Meaning, type Proverb, type SpellCheckResult, TDK, TDKError, TDKNetworkError, type TDKResponse, type TDKRule, TDKValidationError, type WiktionaryEntry, type WordAnalysis, type WordComparison, type WordComparisonSide, type WordInfo, type WordOfTheDay };
|