From 6c7bfd42bc1128f5969e9e40b23d6b828601f7cb Mon Sep 17 00:00:00 2001 From: Shulhan Date: Sun, 31 Jan 2021 04:56:36 +0700 Subject: all: rewrite the server This commit move the directClient to different repository called kamusku and changes the module name from kamusku to kamusd. --- .gcloudignore | 6 +- .gitignore | 12 +- LICENSE | 2 +- Makefile | 70 ++-- README.adoc | 112 ------ README.md | 66 ++++ _www-kamusku/assets/github.svg | 1 - _www-kamusku/assets/linux.svg | 7 - _www-kamusku/assets/macos.svg | 7 - _www-kamusku/assets/windows.svg | 7 - _www-kamusku/index.html | 311 ----------------- _www-kamusku/index.js | 126 ------- _www-kamusku/kbbiclient.js | 35 -- _www/admin.tmpl | 15 + _www/assets/github.svg | 1 + _www/assets/linux.svg | 7 + _www/assets/macos.svg | 7 + _www/assets/windows.svg | 7 + _www/index.html | 229 ++++++++++++ _www/index.js | 126 +++++++ _www/kamusku.js | 35 ++ active_client.go | 12 +- api_client.go | 25 +- api_client_test.go | 39 ++- client.go | 40 +-- cmd/bot-kamusku/app.yaml | 9 - cmd/bot-kamusku/main.go | 43 --- cmd/kamusd/app.yaml | 8 + cmd/kamusd/main.go | 38 ++ cmd/kamusku-telegram-bot/app.yaml | 9 + cmd/kamusku-telegram-bot/main.go | 43 +++ cmd/kbbi/main.go | 108 ------ cmd/www-kamusku/app.yaml | 8 - cmd/www-kamusku/main.go | 36 -- daftar_kata.go | 17 - definisi_kata.go | 94 ----- definisi_response.go | 21 -- dictionary.go | 178 ++++++++++ dictionary_test.go | 32 ++ direct_client.go | 386 --------------------- direct_client_test.go | 29 -- generate.go | 7 - go.mod | 10 +- go.sum | 35 +- internal/cmd/mergedic/main.go | 2 +- kamus_cache.go | 178 ---------- kamus_cache_test.go | 32 -- kamusd.go | 26 ++ kamusd_test.go | 55 +++ kamusku.go | 52 --- kamusku_test.go | 53 --- kata.go | 98 ------ kata_test.go | 66 ---- memfs.go | 9 + server.go | 111 +++--- telegram_bot.go | 32 +- testdata/entri.html | 408 ---------------------- testdata/entri_analisa.html | 342 ------------------ testdata/kbbi_dasar.html | 707 -------------------------------------- 59 files changed, 1086 insertions(+), 3501 deletions(-) delete mode 100644 README.adoc create mode 100644 README.md delete mode 100644 _www-kamusku/assets/github.svg delete mode 100644 _www-kamusku/assets/linux.svg delete mode 100644 _www-kamusku/assets/macos.svg delete mode 100644 _www-kamusku/assets/windows.svg delete mode 100644 _www-kamusku/index.html delete mode 100644 _www-kamusku/index.js delete mode 100644 _www-kamusku/kbbiclient.js create mode 100644 _www/admin.tmpl create mode 100644 _www/assets/github.svg create mode 100644 _www/assets/linux.svg create mode 100644 _www/assets/macos.svg create mode 100644 _www/assets/windows.svg create mode 100644 _www/index.html create mode 100644 _www/index.js create mode 100644 _www/kamusku.js delete mode 100644 cmd/bot-kamusku/app.yaml delete mode 100644 cmd/bot-kamusku/main.go create mode 100644 cmd/kamusd/app.yaml create mode 100644 cmd/kamusd/main.go create mode 100644 cmd/kamusku-telegram-bot/app.yaml create mode 100644 cmd/kamusku-telegram-bot/main.go delete mode 100644 cmd/kbbi/main.go delete mode 100644 cmd/www-kamusku/app.yaml delete mode 100644 cmd/www-kamusku/main.go delete mode 100644 daftar_kata.go delete mode 100644 definisi_kata.go delete mode 100644 definisi_response.go create mode 100644 dictionary.go create mode 100644 dictionary_test.go delete mode 100644 direct_client.go delete mode 100644 direct_client_test.go delete mode 100644 generate.go delete mode 100644 kamus_cache.go delete mode 100644 kamus_cache_test.go create mode 100644 kamusd.go create mode 100644 kamusd_test.go delete mode 100644 kamusku.go delete mode 100644 kamusku_test.go delete mode 100644 kata.go delete mode 100644 kata_test.go create mode 100644 memfs.go delete mode 100644 testdata/entri.html delete mode 100644 testdata/entri_analisa.html delete mode 100644 testdata/kbbi_dasar.html diff --git a/.gcloudignore b/.gcloudignore index 199e6d9..7815628 100644 --- a/.gcloudignore +++ b/.gcloudignore @@ -22,4 +22,8 @@ # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE -*.out \ No newline at end of file +*.out + +_bin +_www +daftar_kata_dasar diff --git a/.gitignore b/.gitignore index a143412..3d5a48e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,11 @@ -/_www-kamusku/bin/* -/bot-kamusku -/bot-kamusku-linux-amd64 -/cmd/www-kamusku/static.go +/_www/bin/* +/kamusd +/kamusku-telegram-bot +/cmd/kamusd/static.go /internal/cmd/mergedic/id_ID.dic /internal/cmd/mergedic/id_ID.dic.new /kamus.gob /kamus.gob.new -/kamusku +/kamusd /testdata/kamus.gob /testdata/kamus.gob.new -/www-kamusku -/www-kamusku-linux-amd64 diff --git a/LICENSE b/LICENSE index 85e7c0d..7e49067 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2020, M. Shulhan (m.shulhan@gmail.com). +Copyright 2020, M. Shulhan (ms@kilabit.info). All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Makefile b/Makefile index 6c9507c..19a623f 100644 --- a/Makefile +++ b/Makefile @@ -1,64 +1,38 @@ -## Copyright 2020, Shulhan . All rights reserved. +## Copyright 2020, Shulhan . All rights reserved. ## Use of this source code is governed by a BSD-style ## license that can be found in the LICENSE file. -.PHONY: all build lint test install release deploy -.PHONY: dev-server +.PHONY: all test check +.PHONY: build-linux_amd64 +.PHONY: deploy deploy-kamusd deploy-telegram-bot +.PHONY: run-kamusd -all: build lint test +BIN_LINUX_AMD64 := _bin/linux_amd64 -build: - go build ./... - -lint: - golangci-lint run --enable-all \ - --disable=dupl \ - --disable=funlen \ - --disable=godox \ - --disable=gomnd \ - --disable=wsl \ - --disable=gocognit \ - --disable=goerr113 \ - --disable=testpackage \ - ./... +all: test check test: - go test ./... + go test -race -p=1 ./... -install: - go install ./cmd/kbbi/ +check: + golangci-lint run ./... -## -## Release tasks -## +build-linux_amd64: + mkdir -p $(BIN_LINUX_AMD64) + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -o $(BIN_LINUX_AMD64)/ ./cmd/... -release: - mkdir -p _content/bin/ - rm _content/bin/kbbi-* - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ./cmd/kbbi && \ - gzip kbbi && \ - mv kbbi.gz _content/bin/kbbi-linux-amd64.gz - CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build ./cmd/kbbi && \ - gzip kbbi && \ - mv kbbi.gz _content/bin/kbbi-darwin-amd64.gz - CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build ./cmd/kbbi && \ - zip -m kbbi.zip kbbi.exe && \ - mv kbbi.zip _content/bin/kbbi-windows-amd64.zip +deploy: build-linux_amd64 -deploy: - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ - go build -o www-kamusku-linux-amd64 ./cmd/www-kamusku/ - rsync --progress ./www-kamusku-linux-amd64 www-kamusku:~/bin/www-kamusku - rsync --progress --recursive ./_www-kamusku/ www-kamusku:~/bin/_www-kamusku/ +deploy-kamusd: + rsync --progress $(BIN_LINUX_AMD64)/kamusd www-kamusku:~/bin/kamusd -deploy-bot: - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ - go build -o bot-kbbi-linux-amd64 ./cmd/bot-kbbi/ - rsync --progress ./bot-kbbi-linux-amd64 www-kamusku:~/bin/bot-kbbi +deploy-telegram-bot: + rsync --progress $(BIN_LINUX_AMD64)/kamusku-telegram-bot www-kamusku:~/bin/kamusku-telegram-bot ## -## Development task +## Development tasks. ## -dev-server: - DEBUG=1 go run ./cmd/www-kamusku +run-kamusd: + DEBUG=2 go run ./cmd/kamusd diff --git a/README.adoc b/README.adoc deleted file mode 100644 index d3127b6..0000000 --- a/README.adoc +++ /dev/null @@ -1,112 +0,0 @@ -= kamusku - -Proyek sumber terbuka implementasi antar-muka perintah dan API untuk Kamus -Besar Bahasa Indonesia. - - -== Program kbbi - -Program kbbi yaitu antar-muka untuk mencari definisi dari kata lewat baris -perintah. - -Program ini sangat sederhana, caranya yaitu dengan memberikan kata yang dicari -setelah nama program, misalnya, - ----- -$ kbbi kamus,bahasa ----- - -Maka akan mencetak definisi dari kata "kamus" dan "bahasa" ke layar, - ----- -=== bahasa - Definisi #1: sistem lambang bunyi yang arbitrer, yang digunakan oleh - anggota suatu masyarakat untuk bekerja sama, berinteraksi, dan - mengidentifikasikan diri - Kelas #1: Nomina: kata benda - Kelas #2: Linguistik: - - - Definisi #2: percakapan (perkataan) yang baik; tingkah laku yang baik; sopan santun - Kelas #1: Nomina: kata benda - Contoh #1: baik budi --nya - - ... - -=== kamus - Definisi #1: karya rujukan atau acuan dalam bentuk cetak maupun digital yang - memuat kata dan ungkapan, dapat disusun menurut abjad atau tema, berisi - keterangan tentang makna, pemakaian, atau terjemahan - Kelas #1: Nomina: kata benda - - Definisi #2: buku yang memuat kumpulan istilah atau nama yang disusun - menurut abjad beserta penjelasan tentang makna dan pemakaiannya - Kelas #1: Nomina: kata benda - - ... ----- - -Unduh program KBBI untuk sistem operasi Anda, - -* https://kilabit.info/project/kbbi/bin/kbbi-linux-amd64.gz[Linux 64bit] -* https://kilabit.info/project/kbbi/bin/kbbi-darwin-amd64.gz[macOS 64bit] -* https://kilabit.info/project/kbbi/bin/kbbi-windows-amd64.gz[Windows 64bit] - - -== Bot Telegram - -Dengan tersedianya API, membuka banyak implementasi terbuka lain, salah -satunya yaitu Bot untuk aplikasi Telegram: https://t.me/KamuskuBot - -Untuk saat ini, KamuskuBot hanya punya satu perintah yaitu "/definisi". Cara -menggunakan perintah ini hampir sama dengan program kbbi yaitu dengan -memberikan kata yang dicari, contohnya, - ----- -/definisi kamus,bahasa ----- - - -== KBBI API - -KBBI API adalah jantung dari semua implementasi di atas dan pencarian definisi -kata di bawah. KBBI API dapat diakses menggunakan HTTP lewat URL berikut: -https://kilabit.info/project/kbbi/api - - -=== API Definisi - -HTTP API untuk mencari definisi dari satu atau lebih kata. - -Format permintaan, - ----- -GET /definisi?kata=,... ----- - -Format respons dalam JSON, - ----- -{ - "": { - "dasar": "", - "pesan": "", - "definisi": [{ - "isi": "", - "kelas": [], - "contoh": [] - }, - ... - ] - } -} ----- - -Jika kata tidak ditemukan atau bila kata bukan kata baku, bagian "pesan" akan -berisi keterangan yang menjelaskan galat dari pencarian. - -Berikut contoh pemanggilan API untuk mencari definisi dari kata "kamus", -"bahaza" (kata tidak ditemukan), dan "analisa" (kata tidak baku): - -https://kilabit.info/project/kbbi/api/definisi?kata=kamus,bahaza,analisa - -Sekian, selamat meretas! diff --git a/README.md b/README.md new file mode 100644 index 0000000..329b04f --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# kamusd + +Proyek sumber terbuka implementasi HTTP API untuk Kamus Besar Bahasa +Indonesia. + + +## HTTP API + +Kamusku HTTP API adalah jantung dari semua implementasi di atas dan pencarian +definisi kata di bawah. +Kamusku HTTP API dapat diakses menggunakan HTTP lewat URL berikut: `/api` + + +### API Definisi + +HTTP API untuk mencari definisi dari satu atau lebih kata. + +Format permintaan, + +``` +GET /api/definisi?kata=,... +``` + +Format respon dalam JSON, + +``` +{ + "": { + "dasar": "", + "pesan": "", + "definisi": [{ + "isi": "", + "kelas": [], + "contoh": [] + }, + ... + ] + } +} +``` + +Jika kata tidak ditemukan atau bila kata bukan kata baku, bagian "pesan" akan +berisi keterangan yang menjelaskan galat dari pencarian. + +Berikut contoh pemanggilan API untuk mencari definisi dari kata "kamus", +"bahaza" (kata tidak ditemukan), dan "analisa" (kata tidak baku): + +``` +/api/definisi?kata=kamus,bahaza,analisa +``` + +## KamuskuBot - Bot Telegram + +Dengan tersedianya API, membuka banyak implementasi terbuka lain, salah +satunya yaitu Bot untuk aplikasi Telegram: https://t.me/KamuskuBot + +Untuk saat ini, KamuskuBot hanya punya satu perintah yaitu "/definisi". Cara +menggunakan perintah ini hampir sama dengan program kamusku yaitu dengan +memberikan kata yang dicari, contohnya, + +``` +/definisi kamus,bahasa +``` + + +Sekian, selamat meretas! diff --git a/_www-kamusku/assets/github.svg b/_www-kamusku/assets/github.svg deleted file mode 100644 index 3899712..0000000 --- a/_www-kamusku/assets/github.svg +++ /dev/null @@ -1 +0,0 @@ -GitHub icon \ No newline at end of file diff --git a/_www-kamusku/assets/linux.svg b/_www-kamusku/assets/linux.svg deleted file mode 100644 index 4c2bbed..0000000 --- a/_www-kamusku/assets/linux.svg +++ /dev/null @@ -1,7 +0,0 @@ - - -IcoFont Icons -brand-linux - - - \ No newline at end of file diff --git a/_www-kamusku/assets/macos.svg b/_www-kamusku/assets/macos.svg deleted file mode 100644 index 701ef59..0000000 --- a/_www-kamusku/assets/macos.svg +++ /dev/null @@ -1,7 +0,0 @@ - - -IcoFont Icons -brand-mac-os - - - \ No newline at end of file diff --git a/_www-kamusku/assets/windows.svg b/_www-kamusku/assets/windows.svg deleted file mode 100644 index 8995329..0000000 --- a/_www-kamusku/assets/windows.svg +++ /dev/null @@ -1,7 +0,0 @@ - - -IcoFont Icons -brand-windows - - - \ No newline at end of file diff --git a/_www-kamusku/index.html b/_www-kamusku/index.html deleted file mode 100644 index a9a5110..0000000 --- a/_www-kamusku/index.html +++ /dev/null @@ -1,311 +0,0 @@ - - - - Project KBBI - - - - - - - - - - - - - - -
-
- Proyek KBBI -
- -
-

- Proyek sumber terbuka implementasi antar-muka perintah dan API - untuk Kamus Besar Bahasa Indonesia. -

-

- Sumber kode untuk proyek ini dapat diambil di - - tautan berikut - -

- -

Program kbbi

-

- Program kbbi yaitu antar-muka untuk mencari definisi dari kata - lewat baris perintah. -

-

- Program ini sangat sederhana, cara menggunakannya cukup dengan - memberikan kata yang dicari setelah nama program, misalnya, -

-
-$ kbbi kamus,bahasa
-
-

- maka akan mencetak definisi dari kata "kamus" dan "bahasa" ke - layar, -

-
-=== bahasa
-  Definisi #1: sistem lambang bunyi yang arbitrer, yang digunakan oleh
-  anggota suatu masyarakat untuk bekerja sama, berinteraksi, dan
-  mengidentifikasikan diri
-    Kelas #1: Nomina: kata benda
-    Kelas #2: Linguistik: -
-
-  Definisi #2: percakapan (perkataan) yang baik; tingkah laku yang baik; sopan santun
-    Kelas #1: Nomina: kata benda
-    Contoh #1: baik budi --nya
-
-  ...
-
-=== kamus
-  Definisi #1: karya rujukan atau acuan dalam bentuk cetak maupun digital yang memuat kata dan ungkapan, dapat disusun menurut abjad atau tema, berisi keterangan tentang makna, pemakaian, atau terjemahan
-    Kelas #1: Nomina: kata benda
-
-  Definisi #2: buku yang memuat kumpulan istilah atau nama yang disusun menurut abjad beserta penjelasan tentang makna dan pemakaiannya
-    Kelas #1: Nomina: kata benda
-
-  ...
-
- -

- Unduh program KBBI untuk sistem operasi Anda, -

- - - -

Bot Telegram

- -

- Dengan tersedianya API, membuka banyak implementasi terbuka lain, - salah satunya yaitu Bot untuk aplikasi Telegram: - - https://t.me/KamuskuBot - -

-

- Untuk saat ini, KamuskuBot hanya punya satu perintah yaitu - "/definisi". Cara menggunakan perintah ini hampir sama dengan - program kbbi yaitu dengan memberikan kata yang dicari, contohnya, -

-
-/definisi kamus,bahasa
-
- -

KBBI API

- -

- KBBI API adalah jantung dari semua implementasi di atas dan - pencarian definisi kata di bawah. KBBI API dapat diakses - menggunakan HTTP lewat: https://kilabit.info/project/kbbi/api. -

- -

API Definisi

-

HTTP API untuk mencari definisi dari satu atau lebih kata.

-

- Format permintaan, -

-
-GET /definisi?kata=<string>,...
-
- -

- Format respons dalam JSON, -

-
-{
-	"<string>": {
-		"dasar": "<string>",
-		"pesan": "<string>",
-		"definisi": [{
-			"isi": "<string>",
-			"kelas": [<string>],
-			"contoh": [<string>]
-		},
-		...
-		]
-	}
-}
-		
- -

- Jika kata tidak ditemukan atau bila kata bukan kata baku, bagian - "pesan" akan berisi keterangan yang menjelaskan galat dari - pencarian. -

-

- Berikut contoh pemanggilan API untuk mencari definisi dari kata - "kamus", "bahaza" (kata tidak ditemukan), dan "analisa" (kata - tidak baku): - - /api/definisi?kata=kamus,bahaza,analisa - -

- -

Definisi kata

- -
-
- -
-
- -
-
- -

- Catatan: Pisahkan kata dengan koma untuk mencari lebih dari satu - kata. -

- -
- - diff --git a/_www-kamusku/index.js b/_www-kamusku/index.js deleted file mode 100644 index f5f0217..0000000 --- a/_www-kamusku/index.js +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright 2020, Shulhan . All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. - */ - -let kbbiClient = new KBBIClient("") - -function cariDefinisi() { - let kata = document.getElementById("kata").value - kbbiClient.getDefinitions(kata, cariDefinisiCallback) -} - -function cariDefinisiCallback(res) { - let out = document.getElementById("definisi-result") - out.innerHTML = "" - - for (let kata in res) { - let root = document.createElement("div") - printResultKata(root, kata, res[kata]) - out.appendChild(root) - } -} - -function printResultKata(out, kata, defKata) { - let el = document.createElement("h3") - el.appendChild(document.createTextNode(kata)) - out.appendChild(el) - - if (typeof defKata.pesan !== "undefined" && defKata.pesan !== "") { - printPesan(out, defKata.pesan) - return - } - - printKataDasar(out, defKata.dasar) - printDefinitions(out, defKata.definisi) -} - -function printPesan(out, pesan) { - let root = document.createElement("div") - root.appendChild(document.createTextNode(pesan)) - out.appendChild(root) -} - -function printKataDasar(out, kataDasar) { - // Seriously, JavaScript? - if ( - typeof kataDasar === "undefined" || - kataDasar === null || - kataDasar === "" - ) { - return - } - - let root = document.createElement("div") - root.appendChild(document.createTextNode("Kata dasar: ")) - - let italic = document.createElement("i") - italic.appendChild(document.createTextNode(kataDasar)) - root.appendChild(italic) - - out.appendChild(root) -} - -function printDefinitions(out, definitions) { - if (typeof definitions === "undefined" || definitions === null) { - return - } - - for (let x = 0; x < definitions.length; x++) { - let def = definitions[x] - - let root = document.createElement("div") - let el = document.createElement("p") - el.classList.add("definisi") - el.appendChild( - document.createTextNode( - "Definisi #" + (x + 1) + ": " + def.isi + ".", - ), - ) - root.appendChild(el) - - printKelasKata(root, def.kelas) - printContoh(root, def.contoh) - out.appendChild(root) - } -} - -function printKelasKata(out, daftarKelas) { - if (typeof daftarKelas === "undefined") { - return - } - - let root = document.createElement("div") - root.classList.add("kelas-kata") - root.appendChild(document.createTextNode("Kelas,")) - - let el = document.createElement("ul") - for (let x = 0; x < daftarKelas.length; x++) { - let li = document.createElement("li") - li.appendChild(document.createTextNode(daftarKelas[x])) - el.appendChild(li) - } - root.appendChild(el) - out.appendChild(root) -} - -function printContoh(out, examples) { - if (typeof examples === "undefined") { - return - } - - let root = document.createElement("div") - root.classList.add("contoh") - root.appendChild(document.createTextNode("Contoh,")) - - let ul = document.createElement("ul") - - for (let x = 0; x < examples.length; x++) { - let li = document.createElement("li") - li.appendChild(document.createTextNode(examples[x])) - ul.appendChild(li) - } - root.appendChild(ul) - out.appendChild(root) -} diff --git a/_www-kamusku/kbbiclient.js b/_www-kamusku/kbbiclient.js deleted file mode 100644 index ab35633..0000000 --- a/_www-kamusku/kbbiclient.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright 2020, Shulhan . All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. - */ - -class KBBIClient { - constructor(baseURL) { - if (baseURL.length === 0) { - baseURL = "https://kilabit.info/project/kbbi" - } - this.baseURL = baseURL - } - - getDefinitions(words, cb) { - if (words.length === 0) { - return - } - - let params = "kata=" + words - let xhr = new XMLHttpRequest() - - xhr.addEventListener("load", function() { - cb(JSON.parse(xhr.responseText)) - }) - - xhr.open("GET", this.baseURL + "/api/definisi?" + params) - xhr.setRequestHeader( - "Content-Type", - "application/x-www-form-urlencoded", - ) - - xhr.send(null) - } -} diff --git a/_www/admin.tmpl b/_www/admin.tmpl new file mode 100644 index 0000000..a275053 --- /dev/null +++ b/_www/admin.tmpl @@ -0,0 +1,15 @@ + + + + Project KBBI - Admin + + + + + +

Tembolok kamus

+ + + + + diff --git a/_www/assets/github.svg b/_www/assets/github.svg new file mode 100644 index 0000000..3899712 --- /dev/null +++ b/_www/assets/github.svg @@ -0,0 +1 @@ +GitHub icon \ No newline at end of file diff --git a/_www/assets/linux.svg b/_www/assets/linux.svg new file mode 100644 index 0000000..4c2bbed --- /dev/null +++ b/_www/assets/linux.svg @@ -0,0 +1,7 @@ + + +IcoFont Icons +brand-linux + + + \ No newline at end of file diff --git a/_www/assets/macos.svg b/_www/assets/macos.svg new file mode 100644 index 0000000..701ef59 --- /dev/null +++ b/_www/assets/macos.svg @@ -0,0 +1,7 @@ + + +IcoFont Icons +brand-mac-os + + + \ No newline at end of file diff --git a/_www/assets/windows.svg b/_www/assets/windows.svg new file mode 100644 index 0000000..8995329 --- /dev/null +++ b/_www/assets/windows.svg @@ -0,0 +1,7 @@ + + +IcoFont Icons +brand-windows + + + \ No newline at end of file diff --git a/_www/index.html b/_www/index.html new file mode 100644 index 0000000..ce76842 --- /dev/null +++ b/_www/index.html @@ -0,0 +1,229 @@ + + + + Project Kamusku + + + + + + + + + + + + + + +
+
Proyek Kamusku
+ +
+ +

+ Proyek sumber terbuka implementasi HTTP API dan antar-muka perintah untuk Kamus Besar Bahasa + Indonesia. +

+ +

HTTP API

+ +

Kamusku HTTP API adalah jantung dari semua implementasi klien.

+ +

API Definisi

+

HTTP API untuk mencari definisi dari satu atau lebih kata.

+

Format permintaan,

+
+GET /definisi?kata=<string>,...
+
+ +

Format respons dalam JSON,

+
+{
+	"<string>": {
+		"dasar": "<string>",
+		"pesan": "<string>",
+		"definisi": [{
+			"isi": "<string>",
+			"kelas": [<string>],
+			"contoh": [<string>]
+		},
+		...
+		]
+	}
+}
+
+ +

+ Jika kata tidak ditemukan atau bila kata bukan kata baku, bagian "pesan" akan berisi keterangan yang + menjelaskan galat dari pencarian. +

+

+ Berikut contoh pemanggilan API untuk mencari definisi dari kata "kamus", "bahaza" (kata tidak + ditemukan), dan "analisa" (kata tidak baku): + + /api/definisi?kata=kamus,bahaza,analisa + +

+ +

Definisi kata

+ +
+
+ +
+
+ +
+
+ +

Catatan: Pisahkan kata dengan koma untuk mencari lebih dari satu kata.

+ +
+ +

Bot Telegram

+ +

+ Dengan tersedianya API, membuka banyak implementasi terbuka lain, salah satunya yaitu Bot untuk + aplikasi Telegram: + https://t.me/KamuskuBot +

+

+ Untuk saat ini, KamuskuBot hanya punya satu perintah yaitu "/definisi". Cara menggunakan perintah + ini hampir sama dengan program kamusku yaitu dengan memberikan kata yang dicari, contohnya, +

+
+/definisi kamus,bahasa
+
+ +

Program kamusku

+ +

Program kamusku yaitu antar-muka untuk mencari definisi dari kata lewat baris perintah.

+

+ Program ini sangat sederhana, cara menggunakannya cukup dengan memberikan kata yang dicari setelah + nama program, misalnya, +

+
+$ kamusku kamus,bahasa
+
+

maka akan mencetak definisi dari kata "kamus" dan "bahasa" ke layar,

+
+=== bahasa
+  Definisi #1: sistem lambang bunyi yang arbitrer, yang digunakan oleh
+  anggota suatu masyarakat untuk bekerja sama, berinteraksi, dan
+  mengidentifikasikan diri
+    Kelas #1: Nomina: kata benda
+    Kelas #2: Linguistik: -
+
+  Definisi #2: percakapan (perkataan) yang baik; tingkah laku yang baik; sopan santun
+    Kelas #1: Nomina: kata benda
+    Contoh #1: baik budi --nya
+
+  ...
+
+=== kamus
+  Definisi #1: karya rujukan atau acuan dalam bentuk cetak maupun digital yang memuat kata dan ungkapan, dapat disusun menurut abjad atau tema, berisi keterangan tentang makna, pemakaian, atau terjemahan
+    Kelas #1: Nomina: kata benda
+
+  Definisi #2: buku yang memuat kumpulan istilah atau nama yang disusun menurut abjad beserta penjelasan tentang makna dan pemakaiannya
+    Kelas #1: Nomina: kata benda
+
+  ...
+
+ +
+ + diff --git a/_www/index.js b/_www/index.js new file mode 100644 index 0000000..0ab0b10 --- /dev/null +++ b/_www/index.js @@ -0,0 +1,126 @@ +/** + * Copyright 2020, Shulhan . All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +let kamusku = new Kamusku("") + +function cariDefinisi() { + let kata = document.getElementById("kata").value + kamusku.getDefinitions(kata, cariDefinisiCallback) +} + +function cariDefinisiCallback(res) { + let out = document.getElementById("definisi-result") + out.innerHTML = "" + + for (let kata in res) { + let root = document.createElement("div") + printResultKata(root, kata, res[kata]) + out.appendChild(root) + } +} + +function printResultKata(out, kata, defKata) { + let el = document.createElement("h3") + el.appendChild(document.createTextNode(kata)) + out.appendChild(el) + + if (typeof defKata.pesan !== "undefined" && defKata.pesan !== "") { + printPesan(out, defKata.pesan) + return + } + + printKataDasar(out, defKata.dasar) + printDefinitions(out, defKata.definisi) +} + +function printPesan(out, pesan) { + let root = document.createElement("div") + root.appendChild(document.createTextNode(pesan)) + out.appendChild(root) +} + +function printKataDasar(out, kataDasar) { + // Seriously, JavaScript? + if ( + typeof kataDasar === "undefined" || + kataDasar === null || + kataDasar === "" + ) { + return + } + + let root = document.createElement("div") + root.appendChild(document.createTextNode("Kata dasar: ")) + + let italic = document.createElement("i") + italic.appendChild(document.createTextNode(kataDasar)) + root.appendChild(italic) + + out.appendChild(root) +} + +function printDefinitions(out, definitions) { + if (typeof definitions === "undefined" || definitions === null) { + return + } + + for (let x = 0; x < definitions.length; x++) { + let def = definitions[x] + + let root = document.createElement("div") + let el = document.createElement("p") + el.classList.add("definisi") + el.appendChild( + document.createTextNode( + "Definisi #" + (x + 1) + ": " + def.isi + ".", + ), + ) + root.appendChild(el) + + printKelasKata(root, def.kelas) + printContoh(root, def.contoh) + out.appendChild(root) + } +} + +function printKelasKata(out, daftarKelas) { + if (typeof daftarKelas === "undefined") { + return + } + + let root = document.createElement("div") + root.classList.add("kelas-kata") + root.appendChild(document.createTextNode("Kelas,")) + + let el = document.createElement("ul") + for (let x = 0; x < daftarKelas.length; x++) { + let li = document.createElement("li") + li.appendChild(document.createTextNode(daftarKelas[x])) + el.appendChild(li) + } + root.appendChild(el) + out.appendChild(root) +} + +function printContoh(out, examples) { + if (typeof examples === "undefined") { + return + } + + let root = document.createElement("div") + root.classList.add("contoh") + root.appendChild(document.createTextNode("Contoh,")) + + let ul = document.createElement("ul") + + for (let x = 0; x < examples.length; x++) { + let li = document.createElement("li") + li.appendChild(document.createTextNode(examples[x])) + ul.appendChild(li) + } + root.appendChild(ul) + out.appendChild(root) +} diff --git a/_www/kamusku.js b/_www/kamusku.js new file mode 100644 index 0000000..364fa1b --- /dev/null +++ b/_www/kamusku.js @@ -0,0 +1,35 @@ +/** + * Copyright 2020, Shulhan . All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +class Kamusku { + constructor(baseURL) { + if (baseURL.length === 0) { + baseURL = "" + } + this.baseURL = baseURL + } + + getDefinitions(words, cb) { + if (words.length === 0) { + return + } + + let params = "kata=" + words + let xhr = new XMLHttpRequest() + + xhr.addEventListener("load", function() { + cb(JSON.parse(xhr.responseText)) + }) + + xhr.open("GET", this.baseURL + "/api/definisi?" + params) + xhr.setRequestHeader( + "Content-Type", + "application/x-www-form-urlencoded", + ) + + xhr.send(null) + } +} diff --git a/active_client.go b/active_client.go index fed49a6..6e9dbc5 100644 --- a/active_client.go +++ b/active_client.go @@ -1,13 +1,17 @@ -// Copyright 2020, Shulhan . All rights reserved. +// Copyright 2020, Shulhan . All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package kamusku +package kamusd + +import ( + "git.sr.ht/~shulhan/kamusku" +) // // activeClient define an interface for an active client. // type activeClient interface { - CariDefinisi(words []string) (res DefinisiResponse, err error) - ListKataDasar() (kataDasar DaftarKata, err error) + Lookup(words []string) (res kamusku.LookupResponse, err error) + ListRootWords() (rootWords kamusku.Words, err error) } diff --git a/api_client.go b/api_client.go index d85f419..10e487a 100644 --- a/api_client.go +++ b/api_client.go @@ -1,15 +1,18 @@ -// Copyright 2020, Shulhan . All rights reserved. +// Copyright 2020, Shulhan . All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package kamusku +package kamusd import ( + "encoding/json" "fmt" "io/ioutil" "net/http" "net/url" "strings" + + "git.sr.ht/~shulhan/kamusku" ) // @@ -42,8 +45,8 @@ func newAPIClient(url string) (client *apiClient) { // // Lookup the definition of words through server API. // -func (client *apiClient) CariDefinisi(words []string) ( - res DefinisiResponse, err error, +func (client *apiClient) Lookup(words []string) ( + res kamusku.LookupResponse, err error, ) { if len(words) == 0 { return nil, nil @@ -54,19 +57,19 @@ func (client *apiClient) CariDefinisi(words []string) ( req, err := http.NewRequest(http.MethodGet, client.url+pathAPIDefinisi, nil) if err != nil { - return nil, fmt.Errorf("CariDefinisi: %w", err) + return nil, fmt.Errorf("Lookup: %w", err) } req.URL.RawQuery = params.Encode() httpRes, err := client.conn.Do(req) if err != nil { - return nil, fmt.Errorf("CariDefinisi: %w", err) + return nil, fmt.Errorf("Lookup: %w", err) } resBody, err := ioutil.ReadAll(httpRes.Body) if err != nil { - return nil, fmt.Errorf("CariDefinisi: %w", err) + return nil, fmt.Errorf("Lookup: %w", err) } defer httpRes.Body.Close() @@ -75,18 +78,18 @@ func (client *apiClient) CariDefinisi(words []string) ( return res, nil } - err = res.unpack(resBody) + err = json.Unmarshal(resBody, &res) if err != nil { - return nil, fmt.Errorf("CariDefinisi: %w", err) + return nil, fmt.Errorf("Lookup: %w", err) } return res, nil } // -// ListKataDasar list all of the root words in dictionary. +// ListRootWords list all of the root words in dictionary. // -func (client *apiClient) ListKataDasar() (res DaftarKata, err error) { +func (client *apiClient) ListRootWords() (res kamusku.Words, err error) { //TODO: return cached list. return res, nil } diff --git a/api_client_test.go b/api_client_test.go index a7b408a..46dac0c 100644 --- a/api_client_test.go +++ b/api_client_test.go @@ -1,16 +1,17 @@ -// Copyright 2020, Shulhan . All rights reserved. +// Copyright 2020, Shulhan . All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package kamusku +package kamusd import ( "testing" + "git.sr.ht/~shulhan/kamusku" "github.com/shuLhan/share/lib/test" ) -func TestApiClient_CariDefinisi_offline(t *testing.T) { +func TestApiClient_Lookup_offline(t *testing.T) { testServer.offline = true client := newAPIClient(testServerAPI) @@ -18,7 +19,7 @@ func TestApiClient_CariDefinisi_offline(t *testing.T) { cases := []struct { desc string words []string - exp DefinisiResponse + exp kamusku.LookupResponse expError string }{{ desc: "With empty input", @@ -28,13 +29,13 @@ func TestApiClient_CariDefinisi_offline(t *testing.T) { }, { desc: "With valid word in cache", words: []string{"mengeja"}, - exp: DefinisiResponse{ + exp: kamusku.LookupResponse{ "mengeja": testKataMengeja, }, }, { desc: "With duplicate words", words: []string{"mengeja", "mengeja"}, - exp: DefinisiResponse{ + exp: kamusku.LookupResponse{ "mengeja": testKataMengeja, }, }} @@ -42,17 +43,17 @@ func TestApiClient_CariDefinisi_offline(t *testing.T) { for _, c := range cases { t.Logf(c.desc) - got, err := client.CariDefinisi(c.words) + got, err := client.Lookup(c.words) if err != nil { test.Assert(t, "error", c.expError, err.Error(), true) continue } - test.Assert(t, "DefinisiResponse", c.exp, got, true) + test.Assert(t, "kamusku.LookupResponse", c.exp, got, true) } } -func TestApiClient_CariDefinisi_online(t *testing.T) { +func TestApiClient_Lookup_online(t *testing.T) { t.Skip() testServer.offline = false @@ -62,31 +63,31 @@ func TestApiClient_CariDefinisi_online(t *testing.T) { cases := []struct { desc string words []string - exp DefinisiResponse + exp kamusku.LookupResponse expError string }{{ desc: "With empty input", }, { desc: "With valid word in cache", words: []string{"mengeja"}, - exp: DefinisiResponse{ + exp: kamusku.LookupResponse{ "mengeja": testKataMengeja, }, }, { desc: "With duplicate words", words: []string{"mengeja", "mengeja"}, - exp: DefinisiResponse{ + exp: kamusku.LookupResponse{ "mengeja": testKataMengeja, }, }, { desc: "With one of the word not in cache", words: []string{"mengeja", "eja"}, - exp: DefinisiResponse{ + exp: kamusku.LookupResponse{ "mengeja": testKataMengeja, - "eja": &Kata{ - Definisi: []*DefinisiKata{{ - Isi: "lafal huruf satu demi satu", - Kelas: []string{"Verba: kata kerja"}, + "eja": &kamusku.Word{ + Definition: []*kamusku.WordDefinition{{ + Value: "lafal huruf satu demi satu", + Classes: []string{"Verba: kata kerja"}, }}, }, }, @@ -95,7 +96,7 @@ func TestApiClient_CariDefinisi_online(t *testing.T) { for _, c := range cases { t.Logf(c.desc) - got, err := client.CariDefinisi(c.words) + got, err := client.Lookup(c.words) if err != nil { test.Assert(t, "error", c.expError, err.Error(), true) continue @@ -105,6 +106,6 @@ func TestApiClient_CariDefinisi_online(t *testing.T) { t.Logf("got: %s = %+v", k, v) } - test.Assert(t, "DefinisiResponse", c.exp, got, true) + test.Assert(t, "LookupResponse", c.exp, got, true) } } diff --git a/client.go b/client.go index 0e07c5e..c0a10fd 100644 --- a/client.go +++ b/client.go @@ -1,11 +1,13 @@ -// Copyright 2020, Shulhan . All rights reserved. +// Copyright 2020, Shulhan . All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package kamusku +package kamusd import ( "fmt" + + "git.sr.ht/~shulhan/kamusku" ) // @@ -14,7 +16,7 @@ import ( type Client struct { active activeClient api *apiClient - direct *directClient + kbbic *kamusku.KbbiClient } // @@ -23,7 +25,7 @@ type Client struct { func NewClient() (cl *Client, err error) { cl = &Client{} - cl.direct, err = newDirectClient() + cl.kbbic, err = kamusku.NewKbbiClient() if err != nil { return nil, err } @@ -31,7 +33,7 @@ func NewClient() (cl *Client, err error) { cl.api = newAPIClient("") if cl.IsAuthenticated() { - cl.active = cl.direct + cl.active = cl.kbbic } else { cl.active = cl.api } @@ -40,18 +42,16 @@ func NewClient() (cl *Client, err error) { } // -// CariDefinisi lookup definition of words. +// Lookup lookup definition of words. // -func (cl *Client) CariDefinisi(words []string) ( - res DefinisiResponse, err error, -) { +func (cl *Client) Lookup(words []string) (res kamusku.LookupResponse, err error) { if cl.active != nil { - return cl.active.CariDefinisi(words) + return cl.active.Lookup(words) } - res, err = cl.api.CariDefinisi(words) + res, err = cl.api.Lookup(words) if err != nil { - return cl.direct.CariDefinisi(words) + return cl.kbbic.Lookup(words) } return res, nil @@ -62,20 +62,20 @@ func (cl *Client) CariDefinisi(words []string) ( // server. // func (cl *Client) IsAuthenticated() bool { - return cl.direct.isAuthenticated() + return cl.kbbic.IsAuthenticated() } // -// ListKataDasar list all of the root words in dictionary. +// ListRootWords list all of the root words in dictionary. // -func (cl *Client) ListKataDasar() (res DaftarKata, err error) { +func (cl *Client) ListRootWords() (res kamusku.Words, err error) { if cl.active != nil { - return cl.active.ListKataDasar() + return cl.active.ListRootWords() } - res, err = cl.api.ListKataDasar() + res, err = cl.api.ListRootWords() if err != nil { - return cl.direct.ListKataDasar() + return cl.kbbic.ListRootWords() } return res, nil @@ -86,12 +86,12 @@ func (cl *Client) ListKataDasar() (res DaftarKata, err error) { // server. // func (cl *Client) Login(user, pass string) (err error) { - err = cl.direct.login(user, pass) + err = cl.kbbic.Login(user, pass) if err != nil { return fmt.Errorf("Login: %w", err) } - cl.active = cl.direct + cl.active = cl.kbbic return nil } diff --git a/cmd/bot-kamusku/app.yaml b/cmd/bot-kamusku/app.yaml deleted file mode 100644 index 74fd1f5..0000000 --- a/cmd/bot-kamusku/app.yaml +++ /dev/null @@ -1,9 +0,0 @@ -service: bot -runtime: go113 -instance_class: F2 -automatic_scaling: - max_instances: 1 -env_variables: - DEBUG: "2" - TELEGRAM_TOKEN: "1121465148:AAH9vI-DkHUOPGTmy1Js0dxKSHLYIIkXaIE" - TELEGRAM_WEBHOOK_URL: "https://bot-dot-kamuskubot.df.r.appspot.com" diff --git a/cmd/bot-kamusku/main.go b/cmd/bot-kamusku/main.go deleted file mode 100644 index c2f60e2..0000000 --- a/cmd/bot-kamusku/main.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2020, Shulhan . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// -// Program bot-kamusku adalah Telegram Bot yang melayani pencarian definisi -// kata menggunakan perintah pesan di Telegram. -// -package main - -import ( - "log" - "os" - "os/signal" - - "github.com/shuLhan/kamusku" -) - -func main() { - log.SetFlags(0) - - // Use the token and Webhook URL from environment variables. - tgbot, err := kamusku.NewTelegramBot("", "") - if err != nil { - log.Fatal(err) - } - - go func() { - err := tgbot.Start() - if err != nil { - log.Println(err) - } - }() - - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - <-c - - err = tgbot.Stop() - if err != nil { - log.Println(err) - } -} diff --git a/cmd/kamusd/app.yaml b/cmd/kamusd/app.yaml new file mode 100644 index 0000000..876c9f8 --- /dev/null +++ b/cmd/kamusd/app.yaml @@ -0,0 +1,8 @@ +service: default +runtime: go115 +instance_class: F2 +automatic_scaling: + max_instances: 1 +env_variables: + KBBI_SUREL: "m.shulhan@gmail.com" + KBBI_SANDI: "tyuiopKEMDIKBUD5810" diff --git a/cmd/kamusd/main.go b/cmd/kamusd/main.go new file mode 100644 index 0000000..6362ac3 --- /dev/null +++ b/cmd/kamusd/main.go @@ -0,0 +1,38 @@ +// Copyright 2020, Shulhan . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "log" + "os" + "os/signal" + + "git.sr.ht/~shulhan/kamusd" +) + +func main() { + log.SetPrefix("kamusd: ") + + server, err := kamusd.NewServer("") + if err != nil { + log.Fatal(err) + } + + go func() { + err = server.Start() + if err != nil { + log.Println(err) + } + }() + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + <-c + + err = server.Shutdown() + if err != nil { + log.Println(err) + } +} diff --git a/cmd/kamusku-telegram-bot/app.yaml b/cmd/kamusku-telegram-bot/app.yaml new file mode 100644 index 0000000..4760016 --- /dev/null +++ b/cmd/kamusku-telegram-bot/app.yaml @@ -0,0 +1,9 @@ +service: telegram-bot +runtime: go115 +instance_class: F2 +automatic_scaling: + max_instances: 1 +env_variables: + DEBUG: "2" + TELEGRAM_TOKEN: "1121465148:AAH9vI-DkHUOPGTmy1Js0dxKSHLYIIkXaIE" + TELEGRAM_WEBHOOK_URL: "https://kamusku-telegram-bot.df.r.appspot.com" diff --git a/cmd/kamusku-telegram-bot/main.go b/cmd/kamusku-telegram-bot/main.go new file mode 100644 index 0000000..f60f4f6 --- /dev/null +++ b/cmd/kamusku-telegram-bot/main.go @@ -0,0 +1,43 @@ +// Copyright 2020, Shulhan . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +// Program kamusku-telegram-bot adalah Telegram Bot yang melayani pencarian +// definisi kata menggunakan perintah pesan di Telegram. +// +package main + +import ( + "log" + "os" + "os/signal" + + "git.sr.ht/~shulhan/kamusd" +) + +func main() { + log.SetPrefix("kamusku-telegram-bot: ") + + // Use the token and Webhook URL from environment variables. + tgbot, err := kamusd.NewTelegramBot("", "") + if err != nil { + log.Fatal(err) + } + + go func() { + err := tgbot.Start() + if err != nil { + log.Println(err) + } + }() + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + <-c + + err = tgbot.Stop() + if err != nil { + log.Println(err) + } +} diff --git a/cmd/kbbi/main.go b/cmd/kbbi/main.go deleted file mode 100644 index 984596c..0000000 --- a/cmd/kbbi/main.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2020, Shulhan . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// -// Program kbbi adalah antar-muka perintah untuk Kamus Besar Bahasa Indonesia -// (KBBI) menggunakan API. -// -package main - -import ( - "flag" - "fmt" - "log" - "sort" - - "github.com/shuLhan/kamusku" -) - -func main() { - var ( - isListKataDasar bool - surel string - sandi string - ) - - log.SetFlags(0) - log.SetPrefix("kbbi") - - flag.StringVar(&surel, "surel", "", "Nama pengguna") - flag.StringVar(&sandi, "sandi", "", "Sandi pengguna") - flag.BoolVar(&isListKataDasar, "daftar-kata-dasar", false, - "Ambil dan cetak semua kata dasar") - - flag.Parse() - - cl, err := kamusku.NewClient() - if err != nil { - log.Fatal(err) - } - - if len(surel) > 0 && len(sandi) > 0 { - err = cl.Login(surel, sandi) - if err != nil { - log.Fatal(err) - } - } - - if isListKataDasar { - if cl.IsAuthenticated() { - log.Fatal("opsi -daftar-kata-dasar membutuhkan opsi -surel dan -sandi") - } - listKataDasar(cl) - return - } - - resDefinisi, err := cl.CariDefinisi(flag.Args()) - if err != nil { - log.Fatal(err) - } - - for k, kata := range resDefinisi { - err = kata.Err() - if err != nil { - fmt.Printf("!!! %s: %s\n", k, err) - continue - } - - fmt.Println("===", k) - if len(kata.Pesan) != 0 { - fmt.Println(" " + kata.Pesan) - continue - } - if len(kata.Dasar) > 0 { - fmt.Printf(" Kata dasar: %s\n", kata.Dasar) - } - for x, def := range kata.Definisi { - fmt.Printf(" Definisi #%d: %s\n", x+1, def.Isi) - - for y, nomina := range def.Kelas { - fmt.Printf(" Kelas #%d: %s\n", y+1, nomina) - } - for z, contoh := range def.Contoh { - fmt.Printf(" Contoh #%d: %s\n", z+1, contoh) - } - fmt.Println() - } - } -} - -func listKataDasar(cl *kamusku.Client) { - kataDasar, err := cl.ListKataDasar() - if err != nil { - log.Println(err) - } - - list := make([]string, 0, len(kataDasar)) - - for k := range kataDasar { - list = append(list, k) - } - - sort.Strings(list) - - for _, kata := range list { - fmt.Println(kata) - } -} diff --git a/cmd/www-kamusku/app.yaml b/cmd/www-kamusku/app.yaml deleted file mode 100644 index 580f7ea..0000000 --- a/cmd/www-kamusku/app.yaml +++ /dev/null @@ -1,8 +0,0 @@ -service: default -runtime: go113 -instance_class: F2 -automatic_scaling: - max_instances: 1 -env_variables: - KBBI_SUREL: "m.shulhan@gmail.com" - KBBI_SANDI: "tyuiopKEMDIKBUD5810" diff --git a/cmd/www-kamusku/main.go b/cmd/www-kamusku/main.go deleted file mode 100644 index f559594..0000000 --- a/cmd/www-kamusku/main.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2020, Shulhan . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package main - -import ( - "log" - "os" - "os/signal" - - "github.com/shuLhan/kamusku" -) - -func main() { - server, err := kamusku.NewServer("") - if err != nil { - log.Fatal(err) - } - - go func() { - err = server.Start() - if err != nil { - log.Println(err) - } - }() - - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - <-c - - err = server.Shutdown() - if err != nil { - log.Println(err) - } -} diff --git a/daftar_kata.go b/daftar_kata.go deleted file mode 100644 index 6232dcd..0000000 --- a/daftar_kata.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2020, Shulhan . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package kamusku - -type DaftarKata map[string]struct{} - -// -// merge other map into current map. -// -func (dk DaftarKata) merge(in DaftarKata) DaftarKata { - for k := range in { - dk[k] = struct{}{} - } - return dk -} diff --git a/definisi_kata.go b/definisi_kata.go deleted file mode 100644 index b050c6b..0000000 --- a/definisi_kata.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2020, Shulhan . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package kamusku - -import ( - "fmt" - "strings" - - "github.com/shuLhan/share/lib/net/html" - libstrings "github.com/shuLhan/share/lib/strings" -) - -// -// DefinisiKata contains the meaning of word in dictionary, and optional -// attribute for word classifications and examples. -// -type DefinisiKata struct { - Isi string `json:"isi"` - Kelas []string `json:"kelas,omitempty"` - Contoh []string `json:"contoh,omitempty"` -} - -func parseDefinisiKata(in string, li *html.Node) (defKata *DefinisiKata, err error) { - elFont := li.GetFirstChild() - if elFont == nil || elFont.Data != tagNameFont { - return nil, nil - } - elItalic := elFont.GetFirstChild() - if elItalic == nil || elItalic.Data != tagNameItalic { - return nil, nil - } - - defKata = &DefinisiKata{} - - elSpan := elItalic.GetFirstChild() - for elSpan != nil && elSpan.Data == tagNameSpan { - kelas := elSpan.GetAttrValue(attrNameTitle) - if len(kelas) > 0 { - defKata.Kelas = append(defKata.Kelas, kelas) - } - elSpan = elSpan.GetNextSibling() - } - - el := elFont.GetNextSibling() - if el == nil { - return defKata, nil - } - - defKata.Isi = strings.TrimSpace(libstrings.SingleSpace(el.Data)) - - if defKata.Isi == "→" { - defKata.Isi = "" - el = el.GetNextSibling() - if el == nil || el.Data != tagNameAnchor { - return nil, nil - } - el = el.GetFirstChild() - return nil, fmt.Errorf(`%q adalah bentuk tidak baku dari %q`, - in, el.Data) - } - - if defKata.Isi[len(defKata.Isi)-1] != ':' { - return defKata, nil - } - - defKata.Isi = defKata.Isi[:len(defKata.Isi)-1] - - // Parse the example of kata in the next sibling. - el = el.GetNextSibling() - for el != nil { - if el.Data != tagNameFont { - break - } - - elItalic = el.GetFirstChild() - if elItalic.Data != tagNameItalic { - break - } - - elText := elItalic.GetFirstChild() - if elText != nil { - contoh := strings.TrimSpace(elText.Data) - if len(contoh) > 0 && contoh != ";" { - defKata.Contoh = append(defKata.Contoh, elText.Data) - } - } - - el = el.GetNextSibling() - } - - return defKata, nil -} diff --git a/definisi_response.go b/definisi_response.go deleted file mode 100644 index 602d0e4..0000000 --- a/definisi_response.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2020, Shulhan . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package kamusku - -import "encoding/json" - -// -// DefinisiResponse is a response from "/definisi" API. -// Its contains mapping of words and their definitions. -// -type DefinisiResponse map[string]*Kata - -func (res *DefinisiResponse) pack() ([]byte, error) { - return json.Marshal(res) -} - -func (res *DefinisiResponse) unpack(v []byte) (err error) { - return json.Unmarshal(v, res) -} diff --git a/dictionary.go b/dictionary.go new file mode 100644 index 0000000..2dd309f --- /dev/null +++ b/dictionary.go @@ -0,0 +1,178 @@ +// Copyright 2020, Shulhan . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kamusd + +import ( + "bytes" + "encoding/gob" + "errors" + "io/ioutil" + "log" + "os" + "sync" + + "git.sr.ht/~shulhan/kamusku" + libio "github.com/shuLhan/share/lib/io" +) + +const ( + defStorageName = "kamus.gob" +) + +// +// dictionary contains cache of words and its definitions. +// +type dictionary struct { + sync.Mutex + cache map[string]*kamusku.Word + lastSize int + storagePath string +} + +// +// newDictionary create and initialize the cache for dictionary. +// +func newDictionary(storagePath string) (dict *dictionary, err error) { + if len(storagePath) == 0 { + storagePath = defStorageName + } + + dict = &dictionary{ + cache: make(map[string]*kamusku.Word), + storagePath: storagePath, + } + + err = dict.load() + if err != nil { + return nil, err + } + + return dict, nil +} + +// +// lookup the definition of word from cache or nil if not exist. +// +func (dict *dictionary) lookup(word string) (kata *kamusku.Word) { + dict.Lock() + kata = dict.cache[word] + dict.Unlock() + return kata +} + +// +// isChanging will return true if the last cache size is not equal with +// current size. +// +func (dict *dictionary) isChanging() bool { + dict.Lock() + defer dict.Unlock() + return dict.lastSize != len(dict.cache) +} + +// +// load the cached dictionary from storage. +// +func (dict *dictionary) load() (err error) { + dict.Lock() + defer dict.Unlock() + + v, err := ioutil.ReadFile(dict.storagePath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } + return err + } + + r := bytes.NewReader(v) + + dec := gob.NewDecoder(r) + err = dec.Decode(&dict.cache) + if err != nil { + return err + } + + // Clean up. Remove all word that contain "→" as definition. + for k, kata := range dict.cache { + for _, def := range kata.Definition { + if def.Value == "→" { + delete(dict.cache, k) + break + } + } + if len(kata.Definition) == 0 { + delete(dict.cache, k) + } + } + + dict.lastSize = len(dict.cache) + + return nil +} + +// +// set save the definition of word into cache. +// +func (dict *dictionary) set(word string, kata *kamusku.Word) { + if len(word) == 0 || kata == nil { + return + } + + dict.Lock() + dict.cache[word] = kata + dict.Unlock() +} + +// +// store the cache to file only if the storage path is set. +// +func (dict *dictionary) store() (err error) { + if len(dict.storagePath) == 0 { + return nil + } + + dict.Lock() + defer dict.Unlock() + + if len(dict.cache) == 0 { + return nil + } + + newStorage := dict.storagePath + ".new" + + f, err := os.Create(newStorage) + if err != nil { + errc := f.Close() + if errc != nil { + log.Println("dictionary: store: ", err) + } + return err + } + + enc := gob.NewEncoder(f) + err = enc.Encode(&dict.cache) + if err != nil { + errc := f.Close() + if errc != nil { + log.Println("dictionary: store: ", err) + } + return err + } + + errc := f.Close() + if errc != nil { + log.Println("dictionary: store: ", err) + } + + err = libio.Copy(dict.storagePath, newStorage) + if err != nil { + return err + } + + dict.lastSize = len(dict.cache) + + return nil +} diff --git a/dictionary_test.go b/dictionary_test.go new file mode 100644 index 0000000..b9861fe --- /dev/null +++ b/dictionary_test.go @@ -0,0 +1,32 @@ +// Copyright 2020, Shulhan . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kamusd + +import ( + "testing" + + "github.com/shuLhan/share/lib/test" +) + +func TestDictionary_store_load(t *testing.T) { + exp, err := newDictionary(testKamusStorage) + if err != nil { + t.Fatal(err) + } + + exp.set("mengeja", testKataMengeja) + + err = exp.store() + if err != nil { + t.Fatal(err) + } + + got, err := newDictionary(testKamusStorage) + if err != nil { + t.Fatal(err) + } + + test.Assert(t, "store and load", exp, got, true) +} diff --git a/direct_client.go b/direct_client.go deleted file mode 100644 index 61824b1..0000000 --- a/direct_client.go +++ /dev/null @@ -1,386 +0,0 @@ -// Copyright 2020, Shulhan . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package kamusku - -import ( - "bytes" - "encoding/gob" - "errors" - "fmt" - "io/ioutil" - "log" - "net/http" - "net/http/cookiejar" - "net/url" - "os" - "path/filepath" - "strconv" - "strings" - - "github.com/shuLhan/share/lib/debug" - libhttp "github.com/shuLhan/share/lib/http" - "github.com/shuLhan/share/lib/net/html" - "golang.org/x/net/publicsuffix" -) - -const ( - cookieFile = "cookie" - configDir = "kbbi" - maxPageNumber = 501 -) - -// -// directClient for KBBI web using HTTP. -// -type directClient struct { - baseDir string - cookieURL *url.URL - cookies []*http.Cookie - httpc *http.Client -} - -// -// newDirectClient create and initialize new client that connect directly to -// KBBI official website. -// -func newDirectClient() (cl *directClient, err error) { - cookieURL, err := url.Parse(baseURL) - if err != nil { - return nil, fmt.Errorf("newDirectClient: %w", err) - } - - jarOpt := &cookiejar.Options{ - PublicSuffixList: publicsuffix.List, - } - - jar, err := cookiejar.New(jarOpt) - if err != nil { - return nil, fmt.Errorf("newDirectClient: %w", err) - } - - cl = &directClient{ - cookieURL: cookieURL, - httpc: &http.Client{ - Jar: jar, - Timeout: defTimeout, - }, - } - - err = cl.loadCookies() - if err != nil { - return nil, fmt.Errorf("newDirectClient: %w", err) - } - - if cl.cookies != nil { - jar.SetCookies(cookieURL, cl.cookies) - } - - return cl, nil -} - -// -// CariDefinisi dari daftar kata. -// -func (cl *directClient) CariDefinisi(ins []string) ( - res DefinisiResponse, err error, -) { - res = make(DefinisiResponse, len(ins)) - - for _, in := range ins { - _, ok := res[in] - if ok { - continue - } - - kata := &Kata{} - res[in] = kata - - entriURL := baseURL + entriPath + in - httpRes, err := cl.httpc.Get(entriURL) - if err != nil { - kata.err = err - continue - } - - defer httpRes.Body.Close() - - body, err := ioutil.ReadAll(httpRes.Body) - if err != nil { - kata.err = err - continue - } - - if debug.Value >= 2 { - fmt.Printf(">>> HTML body for %s:\n%s", entriURL, body) - } - - err = kata.parseHTMLEntri(in, body) - if err != nil { - kata.err = err - } - - if len(kata.Definisi) == 0 && len(kata.Pesan) == 0 { - kata.Pesan = "Entri tidak ditemukan" - } - } - - return res, nil -} - -// -// ListKataDasar list all of the root words in dictionary. -// -func (cl *directClient) ListKataDasar() (kataDasar DaftarKata, err error) { - params := url.Values{ - paramNameMasukan: []string{paramValueDasar}, - paramNameMasukanLengkap: []string{paramValueDasar}, - } - - urlPage := baseURL + "/Cari/Jenis?" - - kataDasar = make(DaftarKata) - - for pageNumber := 1; pageNumber <= maxPageNumber; pageNumber++ { - params.Set(paramNamePage, strconv.Itoa(pageNumber)) - - req, err := http.NewRequest(http.MethodGet, urlPage+params.Encode(), nil) - if err != nil { - return kataDasar, err - } - - res, err := cl.httpc.Do(req) - if err != nil { - return kataDasar, fmt.Errorf("ListKataDasar: page %d: %w", - pageNumber, err) - } - - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - return kataDasar, fmt.Errorf("ListKataDasar: page %d: %w", - pageNumber, err) - } - - got, err := cl.parseHTMLKataDasar(body) - if err != nil { - return kataDasar, fmt.Errorf("ListKataDasar: page %d: %w", - pageNumber, err) - } - if len(got) == 0 { - break - } - - kataDasar.merge(got) - - log.Printf("ListKataDasar: halaman %d, jumlah kata %d, total kata %d", - pageNumber, len(got), len(kataDasar)) - } - - return kataDasar, nil -} - -// -// isAuthenticated will return true if the client already login; otherwise it -// will return false. -// -func (cl *directClient) isAuthenticated() bool { - return len(cl.cookies) > 0 -} - -// -// login authenticate the client using username and password. -// -func (cl *directClient) login(surel, sandi string) (err error) { - tokenLogin, err := cl.preLogin() - if err != nil { - return fmt.Errorf("Login: %w", err) - } - - params := url.Values{ - paramNameRequestVerificationToken: []string{tokenLogin}, - paramNamePosel: []string{surel}, - paramNameKataSandi: []string{sandi}, - paramNameIngatSaya: []string{paramValueFalse}, - } - - reqBody := strings.NewReader(params.Encode()) - - req, err := http.NewRequest(http.MethodPost, loginURL, reqBody) - if err != nil { - return fmt.Errorf("Login: %w", err) - } - - req.Header.Set(libhttp.HeaderContentType, libhttp.ContentTypeForm) - - res, err := cl.httpc.Do(req) - if err != nil { - return fmt.Errorf("Login: %w", err) - } - - defer res.Body.Close() - - resBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return fmt.Errorf("Login: %w", err) - } - - if res.StatusCode >= http.StatusBadRequest { - return fmt.Errorf("login: %d %s", res.StatusCode, resBody) - } - - cl.cookies = cl.httpc.Jar.Cookies(cl.cookieURL) - cl.setCookies() - cl.saveCookies() - - return nil -} - -// -// setCookies for HTTP request that need an authentication. -// -func (cl *directClient) setCookies() { - cl.httpc.Jar.SetCookies(cl.cookieURL, cl.cookies) -} - -func (cl *directClient) parseHTMLKataDasar(htmlBody []byte) ( - kataDasar DaftarKata, err error, -) { - iter, err := html.Parse(bytes.NewReader(htmlBody)) - if err != nil { - return nil, err - } - - kataDasar = make(DaftarKata) - - for node := iter.Next(); node != nil; node = iter.Next() { - if !node.IsElement() { - continue - } - if node.Data != tagNameAnchor { - continue - } - hrefValue := node.GetAttrValue(attrNameHref) - if !strings.HasPrefix(hrefValue, entriPath) { - continue - } - k := strings.TrimSpace(node.FirstChild.Data) - kataDasar[k] = struct{}{} - } - - return kataDasar, nil -} - -// -// parseHTMLLogin get the token at the form login. -// -func (cl *directClient) parseHTMLLogin(htmlBody []byte) ( - token string, err error, -) { - iter, err := html.Parse(bytes.NewReader(htmlBody)) - if err != nil { - return "", err - } - - for node := iter.Next(); node != nil; node = iter.Next() { - if !node.IsElement() { - continue - } - if node.Data != tagNameInput { - continue - } - - token := node.GetAttrValue(attrNameValue) - if len(token) > 0 { - return token, nil - } - } - - return "", fmt.Errorf("token login not found") -} - -// -// preLogin initialize the client to get the first cookie. -// -func (cl *directClient) preLogin() (token string, err error) { - req, err := http.NewRequest(http.MethodGet, loginURL, nil) - if err != nil { - return "", err - } - - res, err := cl.httpc.Do(req) - if err != nil { - return "", err - } - - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - return "", err - } - - token, err = cl.parseHTMLLogin(body) - if err != nil { - return "", err - } - - return token, nil -} - -// -// loadCookies load the KBBI cookies from file. -// -func (cl *directClient) loadCookies() (err error) { - cl.baseDir, err = os.UserConfigDir() - if err != nil { - return fmt.Errorf("loadCookies: %w", err) - } - - f := filepath.Join(cl.baseDir, configDir, cookieFile) - - _, err = os.Stat(f) - if errors.Is(err, os.ErrNotExist) { - return nil - } - - body, err := ioutil.ReadFile(f) - if err != nil { - return fmt.Errorf("loadCookies: %w", err) - } - - dec := gob.NewDecoder(bytes.NewReader(body)) - - err = dec.Decode(&cl.cookies) - if err != nil { - return fmt.Errorf("loadCookies: %w", err) - } - - return nil -} - -// -// saveCookies store the client cookies to the file for future use. -// -func (cl *directClient) saveCookies() { - err := os.MkdirAll(filepath.Join(cl.baseDir, configDir), 0700) - if err != nil { - log.Println("saveCookies:", err) - } - - f := filepath.Join(cl.baseDir, configDir, cookieFile) - - var buf bytes.Buffer - enc := gob.NewEncoder(&buf) - err = enc.Encode(cl.cookies) - if err != nil { - log.Println("saveCookies: ", err) - } - - err = ioutil.WriteFile(f, buf.Bytes(), 0600) - if err != nil { - log.Println("saveCookies: ", err) - } -} diff --git a/direct_client_test.go b/direct_client_test.go deleted file mode 100644 index 533fc78..0000000 --- a/direct_client_test.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2020, Shulhan . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package kamusku - -import ( - "io/ioutil" - "testing" -) - -func TestDirectClient_parseHTMLKataDasar(t *testing.T) { - htmlBody, err := ioutil.ReadFile("testdata/kbbi_dasar.html") - if err != nil { - t.Fatal(err) - } - - cl, err := newDirectClient() - if err != nil { - t.Fatal(err) - } - - got, err := cl.parseHTMLKataDasar(htmlBody) - if err != nil { - t.Fatal(err) - } - - t.Logf("Kata dasar: %v", got) -} diff --git a/generate.go b/generate.go deleted file mode 100644 index 5176311..0000000 --- a/generate.go +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2020, Shulhan . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:generate go run ./internal/generate - -package kamusku diff --git a/go.mod b/go.mod index 5b0e918..17a358c 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,12 @@ -module github.com/shuLhan/kamusku +module git.sr.ht/~shulhan/kamusd -go 1.13 +go 1.15 require ( - github.com/shuLhan/share v0.15.1-0.20200528164210-c03ddce63f66 - golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e + git.sr.ht/~shulhan/kamusku v0.0.0-00010101000000-000000000000 + github.com/shuLhan/share v0.22.1-0.20210124101421-f76dc891e371 ) //replace github.com/shuLhan/share => ../share + +replace git.sr.ht/~shulhan/kamusku => ../kamusku diff --git a/go.sum b/go.sum index 48dc914..b8995dd 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,33 @@ -github.com/shuLhan/share v0.15.1-0.20200528164210-c03ddce63f66 h1:XsI02DpIEP8ujAd7uRv9DCrnmfwC7NkTwt4fNNNAkOo= -github.com/shuLhan/share v0.15.1-0.20200528164210-c03ddce63f66/go.mod h1:mpa0ub5qmuko/muUlOROOqLCSHKU76GzuAR/sUaSwRo= +git.sr.ht/~shulhan/asciidoctor-go v0.0.0-20201205130914-be765f32b57b/go.mod h1:ejaxKeBMNL5EpP2zjRP4B8zuOr+MM4ZyGwE3y7807WI= +git.sr.ht/~shulhan/asciidoctor-go v0.0.0-20201226102329-36285ff15434/go.mod h1:ejaxKeBMNL5EpP2zjRP4B8zuOr+MM4ZyGwE3y7807WI= +git.sr.ht/~shulhan/ciigo v0.3.0/go.mod h1:Y5FvSiJg88qshoR1ktj4fLzM5sk1pZcV0kJGU8GAuTo= +git.sr.ht/~shulhan/ciigo v0.3.1-0.20210109200358-c23bd42ef521/go.mod h1:DLyaapVphRtqry80iqw+luWAKepHtbDmbvxqFmulcko= +github.com/shuLhan/share v0.20.2-0.20201122173411-e8b3bf5ee6e9/go.mod h1:oBv+CGHG6u4Sa71+nJJJji8mCgPAadywjsB3I3k/b0o= +github.com/shuLhan/share v0.20.2-0.20201205202022-66069b9e49fe/go.mod h1:oBv+CGHG6u4Sa71+nJJJji8mCgPAadywjsB3I3k/b0o= +github.com/shuLhan/share v0.22.0/go.mod h1:u9caerexlcxmPVDttj7PnkxCBDY6yBRTZ+gGR+1tO98= +github.com/shuLhan/share v0.22.1-0.20210109185915-0490a19341d9/go.mod h1:u9caerexlcxmPVDttj7PnkxCBDY6yBRTZ+gGR+1tO98= +github.com/shuLhan/share v0.22.1-0.20210124101421-f76dc891e371 h1:5UPgRXvrL9YmiydMG72xJYE8LuYS1EcjZQrBqhSX064= +github.com/shuLhan/share v0.22.1-0.20210124101421-f76dc891e371/go.mod h1:y4+p5vUmKNNhMMhU6yGgE6QxTgJxA4nv6OOq+cIf7wU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210108172913-0df2131ae363/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/internal/cmd/mergedic/main.go b/internal/cmd/mergedic/main.go index 7cdba38..d9612ca 100644 --- a/internal/cmd/mergedic/main.go +++ b/internal/cmd/mergedic/main.go @@ -1,4 +1,4 @@ -// Copyright 2020, Shulhan . All rights reserved. +// Copyright 2020, Shulhan . All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/kamus_cache.go b/kamus_cache.go deleted file mode 100644 index 52eb1f4..0000000 --- a/kamus_cache.go +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2020, Shulhan . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package kamusku - -import ( - "bytes" - "encoding/gob" - "errors" - "io/ioutil" - "log" - "os" - "sync" - - libio "github.com/shuLhan/share/lib/io" -) - -const ( - defStorageName = "kamus.gob" -) - -// -// kamusCache contains cache of words and its definitions. -// -type kamusCache struct { - sync.Mutex - cache map[string]*Kata - lastSize int - - storagePath string -} - -// -// newKamusCache create and initialize the cache for dictionary. -// -func newKamusCache(storagePath string) (kamusc *kamusCache, err error) { - if len(storagePath) == 0 { - storagePath = defStorageName - } - - kamusc = &kamusCache{ - cache: make(map[string]*Kata), - storagePath: storagePath, - } - - err = kamusc.load() - if err != nil { - return nil, err - } - - return kamusc, nil -} - -// -// get the definition of word from cache or nil if not exist. -// -func (kamus *kamusCache) get(word string) (kata *Kata) { - kamus.Lock() - kata = kamus.cache[word] - kamus.Unlock() - return kata -} - -// -// isChanging will return true if the last cache size is not equal with -// current size. -// -func (kamus *kamusCache) isChanging() bool { - kamus.Lock() - defer kamus.Unlock() - return kamus.lastSize != len(kamus.cache) -} - -// -// load the cached dictionary from storage. -// -func (kamus *kamusCache) load() (err error) { - kamus.Lock() - defer kamus.Unlock() - - v, err := ioutil.ReadFile(kamus.storagePath) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - return nil - } - return err - } - - r := bytes.NewReader(v) - - dec := gob.NewDecoder(r) - err = dec.Decode(&kamus.cache) - if err != nil { - return err - } - - // Clean up. Remove all word that contain "→" as definition. - for k, kata := range kamus.cache { - for _, def := range kata.Definisi { - if def.Isi == "→" { - delete(kamus.cache, k) - break - } - } - if len(kata.Definisi) == 0 { - delete(kamus.cache, k) - } - } - - kamus.lastSize = len(kamus.cache) - - return nil -} - -// -// set save the definition of word into cache. -// -func (kamus *kamusCache) set(word string, kata *Kata) { - if len(word) == 0 || kata == nil { - return - } - - kamus.Lock() - kamus.cache[word] = kata - kamus.Unlock() -} - -// -// store the cache to file only if the storage path is set. -// -func (kamus *kamusCache) store() (err error) { - if len(kamus.storagePath) == 0 { - return nil - } - - kamus.Lock() - defer kamus.Unlock() - - if len(kamus.cache) == 0 { - return nil - } - - newStorage := kamus.storagePath + ".new" - - f, err := os.Create(newStorage) - if err != nil { - errc := f.Close() - if errc != nil { - log.Println("kamusCache: store: ", err) - } - return err - } - - enc := gob.NewEncoder(f) - err = enc.Encode(&kamus.cache) - if err != nil { - errc := f.Close() - if errc != nil { - log.Println("kamusCache: store: ", err) - } - return err - } - - errc := f.Close() - if errc != nil { - log.Println("kamusCache: store: ", err) - } - - err = libio.Copy(kamus.storagePath, newStorage) - if err != nil { - return err - } - - kamus.lastSize = len(kamus.cache) - - return nil -} diff --git a/kamus_cache_test.go b/kamus_cache_test.go deleted file mode 100644 index 09a51cf..0000000 --- a/kamus_cache_test.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2020, Shulhan . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package kamusku - -import ( - "testing" - - "github.com/shuLhan/share/lib/test" -) - -func TestKamusCache_store_load(t *testing.T) { - exp, err := newKamusCache(testKamusStorage) - if err != nil { - t.Fatal(err) - } - - exp.set("mengeja", testKataMengeja) - - err = exp.store() - if err != nil { - t.Fatal(err) - } - - got, err := newKamusCache(testKamusStorage) - if err != nil { - t.Fatal(err) - } - - test.Assert(t, "store and load", exp, got, true) -} diff --git a/kamusd.go b/kamusd.go new file mode 100644 index 0000000..b9340e8 --- /dev/null +++ b/kamusd.go @@ -0,0 +1,26 @@ +// Copyright 2020, Shulhan . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +// Package kamusd provide HTTP server API for Kamusku client and Kamusku +// Telegram bot. +// +package kamusd + +import "time" + +const ( + defListen = ":3394" + defServerAPI = "https://kamuskubot.df.r.appspot.com" + defTimeout = 20 * time.Second + + envPort = "PORT" + envKbbiSandi = "KBBI_SANDI" + envKbbiSurel = "KBBI_SUREL" + + jsonEmptyObject = "{}" + + pathAPIDefinisi = "/api/definisi" + paramNameKata = "kata" +) diff --git a/kamusd_test.go b/kamusd_test.go new file mode 100644 index 0000000..ba71aac --- /dev/null +++ b/kamusd_test.go @@ -0,0 +1,55 @@ +// Copyright 2020, Shulhan . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kamusd + +import ( + "log" + "os" + "testing" + + "git.sr.ht/~shulhan/kamusku" +) + +const ( + testServerAPI = "http://127.0.0.1" + defListen + testKamusStorage = "testdata/kamus.gob" +) + +//nolint: gochecknoglobals +var ( + testServer *Server + + testKataMengeja = &kamusku.Word{ + Root: "eja", + Definition: []*kamusku.WordDefinition{{ + Value: "melafalkan (menyebutkan) huruf-huruf satu demi satu", + Classes: []string{"Verba: kata kerja"}, + Examples: []string{ + `kita ~ kata “dapat” dengan “d-a-p-a-t”`, + }, + }}, + } +) + +func TestMain(m *testing.M) { + var err error + + // Run the local server to test the apiClient. + testServer, err = NewServer(testKamusStorage) + if err != nil { + log.Fatal(err) + } + + testServer.kamus.set("mengeja", testKataMengeja) + + go func() { + err := testServer.Start() + if err != nil { + log.Fatal(err) + } + }() + + os.Exit(m.Run()) +} diff --git a/kamusku.go b/kamusku.go deleted file mode 100644 index 151d85d..0000000 --- a/kamusku.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2020, Shulhan . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// -// Package kamusku is Go client for Kamus Besar Bahasa Indonesia at -// kbbi.kemdikbud.go.id. -// -package kamusku - -import "time" - -const ( - hostname = "kbbi.kemdikbud.go.id" - baseURL = "https://" + hostname - loginURL = baseURL + "/Account/Login" - entriPath = "/entri/" - - defServerAPI = "https://kamuskubot.df.r.appspot.com" - envPort = "PORT" - pathAPIDefinisi = "/api/definisi" - - attrNameClass = "class" - attrNameHref = "href" - attrNameTitle = "title" - attrNameValue = "value" - - attrValueRootWord = "rootword" - - tagNameAnchor = "a" - tagNameFont = "font" - tagNameHeader2 = "h2" - tagNameInput = "input" - tagNameItalic = "i" - tagNameOrderedList = "ol" - tagNameSpan = "span" - tagNameUnorderedList = "ul" - - paramNameIngatSaya = "IngatSaya" - paramNameKata = "kata" - paramNameKataSandi = "KataSandi" - paramNameMasukan = "masukan" - paramNameMasukanLengkap = "masukanLengkap" - paramNamePage = "page" - paramNamePosel = "Posel" - paramNameRequestVerificationToken = "__RequestVerificationToken" //nolint: gosec - - paramValueDasar = "dasar" - paramValueFalse = "false" - - defTimeout = 20 * time.Second -) diff --git a/kamusku_test.go b/kamusku_test.go deleted file mode 100644 index 9967c1c..0000000 --- a/kamusku_test.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2020, Shulhan . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package kamusku - -import ( - "log" - "os" - "testing" -) - -const ( - testServerAPI = "http://127.0.0.1" + defListen - testKamusStorage = "testdata/kamus.gob" -) - -//nolint: gochecknoglobals -var ( - testServer *Server - - testKataMengeja = &Kata{ - Dasar: "eja", - Definisi: []*DefinisiKata{{ - Isi: "melafalkan (menyebutkan) huruf-huruf satu demi satu", - Kelas: []string{"Verba: kata kerja"}, - Contoh: []string{ - `kita ~ kata “dapat” dengan “d-a-p-a-t”`, - }, - }}, - } -) - -func TestMain(m *testing.M) { - var err error - - // Run the local server to test the apiClient. - testServer, err = NewServer(testKamusStorage) - if err != nil { - log.Fatal(err) - } - - testServer.kamus.set("mengeja", testKataMengeja) - - go func() { - err := testServer.Start() - if err != nil { - log.Fatal(err) - } - }() - - os.Exit(m.Run()) -} diff --git a/kata.go b/kata.go deleted file mode 100644 index 8cf6466..0000000 --- a/kata.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2020, Shulhan . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package kamusku - -import ( - "bytes" - - "github.com/shuLhan/share/lib/net/html" -) - -// -// Err return an error from retrieving definition. -// -func (kata *Kata) Err() error { - return kata.err -} - -// -// Kata store the single root word and its definitions. -// -type Kata struct { - Dasar string `json:"dasar,omitempty"` - Definisi []*DefinisiKata `json:"definisi"` - - // Pesan will contains the message when the word is not found or the - // word is informal (kata tidak baku). - Pesan string `json:"pesan,omitempty"` - - err error -} - -// -// parseHTMLEntri parse HTML body from "/entri/" page to find the -// definition of the word. -// -func (kata *Kata) parseHTMLEntri(in string, htmlBody []byte) (err error) { - iter, err := html.Parse(bytes.NewReader(htmlBody)) - if err != nil { - return err - } - - for node := iter.Next(); node != nil; node = iter.Next() { - if !node.IsElement() { - continue - } - - switch node.Data { - case tagNameHeader2: - kata.parseKataDasar(node) - - case tagNameOrderedList, tagNameUnorderedList: - li := node.GetFirstChild() - for li != nil { - defKata, err := parseDefinisiKata(in, li) - if err != nil { - kata.Pesan = err.Error() - err = nil - break - } - if defKata == nil { - break - } - kata.Definisi = append(kata.Definisi, defKata) - li = li.GetNextSibling() - } - next := node.GetNextSibling() - iter.SetNext(next) - } - } - - return nil -} - -// -// parseKataDasar given an HMTL element "h2" find a possible root word and -// return true; otherwise it will return false. -// -func (kata *Kata) parseKataDasar(h2 *html.Node) bool { - el := h2.GetFirstChild() - if el.Data != tagNameSpan { - return false - } - v := el.GetAttrValue(attrNameClass) - if v != attrValueRootWord { - return false - } - - el = el.GetFirstChild() - if el.Data != tagNameAnchor { - return false - } - el = el.GetFirstChild() - kata.Dasar = el.Data - - return true -} diff --git a/kata_test.go b/kata_test.go deleted file mode 100644 index d471575..0000000 --- a/kata_test.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2020, Shulhan . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package kamusku - -import ( - "io/ioutil" - "testing" - - "github.com/shuLhan/share/lib/test" -) - -func TestKata_parseHTMLEntri(t *testing.T) { - cases := []struct { - infile string - cari string - exp *Kata - }{{ - infile: "testdata/entri.html", - cari: "informasi", - exp: &Kata{ - Definisi: []*DefinisiKata{{ - Isi: "penerangan", - Kelas: []string{"Nomina: kata benda"}, - }, { - Isi: "pemberitahuan; kabar atau berita tentang sesuatu", - Kelas: []string{"Nomina: kata benda"}, - }, { - Isi: "keseluruhan makna yang menunjang amanat yang " + - "terlihat dalam bagian-bagian " + - "amanat itu", - Kelas: []string{ - "Nomina: kata benda", - "Linguistik: -", - }, - }}, - }, - }, { - infile: "testdata/entri_analisa.html", - cari: "analisa", - exp: &Kata{ - Pesan: `"analisa" adalah bentuk tidak baku dari "analisis"`, - }, - }} - - for _, c := range cases { - htmlBody, err := ioutil.ReadFile(c.infile) - if err != nil { - t.Fatal(err) - } - - got := new(Kata) - - err = got.parseHTMLEntri(c.cari, htmlBody) - if err != nil { - t.Fatal(err) - } - - for x, def := range c.exp.Definisi { - test.Assert(t, "Definisi", def, got.Definisi[x], true) - } - - test.Assert(t, c.infile, c.exp, got, true) - } -} diff --git a/memfs.go b/memfs.go new file mode 100644 index 0000000..a506835 --- /dev/null +++ b/memfs.go @@ -0,0 +1,9 @@ +// Copyright 2021, Shulhan . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kamusd + +import "github.com/shuLhan/share/lib/memfs" + +var memfsWWW *memfs.MemFS diff --git a/server.go b/server.go index 3e2ebd0..673c211 100644 --- a/server.go +++ b/server.go @@ -1,41 +1,37 @@ -// Copyright 2020, Shulhan . All rights reserved. +// Copyright 2020, Shulhan . All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package kamusku +package kamusd import ( "context" + "encoding/json" "fmt" "log" "math/rand" - stdhttp "net/http" + "net/http" "os" "strings" "sync" "time" + "git.sr.ht/~shulhan/kamusku" "github.com/shuLhan/share/lib/ascii" "github.com/shuLhan/share/lib/debug" - "github.com/shuLhan/share/lib/http" -) - -const ( - envKbbiSandi = "KBBI_SANDI" - envKbbiSurel = "KBBI_SUREL" - defListen = ":3394" - emptyResponse = "{}" + libhttp "github.com/shuLhan/share/lib/http" + "github.com/shuLhan/share/lib/memfs" ) // -// Server for KBBI with caching and spell checking functionalities. +// Server for kamusku with caching and spell checking functionalities. // type Server struct { - http *http.Server - kamus *kamusCache + httpd *libhttp.Server + kamus *dictionary // The client that forward request to official KBBI server. - forwardc *directClient + kbbic *kamusku.KbbiClient stopped chan bool wg sync.WaitGroup @@ -49,53 +45,53 @@ type Server struct { // NewServer create and initialize the server with optional path to dictionary // storage. // -func NewServer(kamusStorage string) (server *Server, err error) { +func NewServer(dictionaryStorage string) (server *Server, err error) { address := defListen port := os.Getenv(envPort) if len(port) > 0 { address = ":" + port } - opts := &http.ServerOptions{ - Root: "", + httpdOpts := &libhttp.ServerOptions{ + Options: memfs.Options{ + Root: "_www", + Development: debug.Value >= 2, + }, + Memfs: memfsWWW, Address: address, } - if debug.Value > 0 { - opts.Development = true - } - server = &Server{ stopped: make(chan bool, 1), } - server.kamus, err = newKamusCache(kamusStorage) + server.kamus, err = newDictionary(dictionaryStorage) if err != nil { - return nil, fmt.Errorf("http.NewServer: %w", err) + return nil, fmt.Errorf("NewServer: %w", err) } - server.http, err = http.NewServer(opts) + server.httpd, err = libhttp.NewServer(httpdOpts) if err != nil { - return nil, fmt.Errorf("http.NewServer: %w", err) + return nil, fmt.Errorf("NewServer: %w", err) } - server.forwardc, err = newDirectClient() + server.kbbic, err = kamusku.NewKbbiClient() if err != nil { - return nil, fmt.Errorf("http.NewServer: %w", err) + return nil, fmt.Errorf("NewServer: %w", err) } err = server.registerEndpoints() if err != nil { - return nil, fmt.Errorf("http.NewServer: %w", err) + return nil, fmt.Errorf("NewServer: %w", err) } - if !server.forwardc.isAuthenticated() { + if !server.kbbic.IsAuthenticated() { surel := os.Getenv(envKbbiSurel) sandi := os.Getenv(envKbbiSandi) if len(surel) > 0 && len(sandi) > 0 { - err = server.forwardc.login(surel, sandi) + err = server.kbbic.Login(surel, sandi) if err != nil { - return nil, err + return nil, fmt.Errorf("NewServer: %w", err) } } } @@ -108,16 +104,17 @@ func NewServer(kamusStorage string) (server *Server, err error) { // func (server *Server) Start() (err error) { go server.dumpCacheJob() + server.wg.Add(1) - return server.http.Start() + return server.httpd.Start() } // // Shutdown the HTTP server and save the cache for future use. // func (server *Server) Shutdown() (err error) { - err = server.http.Shutdown(context.TODO()) + err = server.httpd.Shutdown(context.TODO()) server.stopped <- true @@ -162,25 +159,21 @@ func (server *Server) dumpCache() { // handleAdmin is endpoint to manage dictionary cache on the web. // func (server *Server) handleAdmin( - httpRes stdhttp.ResponseWriter, - httpReq *stdhttp.Request, - reqBody []byte, + _ http.ResponseWriter, _ *http.Request, _ []byte, ) (resBody []byte, err error) { return resBody, nil } func (server *Server) handleDefinisi( - httpRes stdhttp.ResponseWriter, - httpReq *stdhttp.Request, - reqBody []byte, + _ http.ResponseWriter, httpReq *http.Request, _ []byte, ) (resBody []byte, err error) { paramKata := httpReq.Form.Get(paramNameKata) if len(paramKata) == 0 { - return []byte(emptyResponse), nil + return []byte(jsonEmptyObject), nil } inputs := strings.Split(paramKata, ",") - res := make(DefinisiResponse, len(inputs)) + res := make(kamusku.LookupResponse, len(inputs)) for _, in := range inputs { in = strings.TrimSpace(in) @@ -188,7 +181,7 @@ func (server *Server) handleDefinisi( continue } - kata := server.kamus.get(in) + kata := server.kamus.lookup(in) if kata != nil { res[in] = kata continue @@ -200,9 +193,8 @@ func (server *Server) handleDefinisi( // The word does not exist in cache, retrieve it from official // website. - fwRes, err := server.forwardc.CariDefinisi([]string{in}) + fwRes, err := server.kbbic.Lookup([]string{in}) if err != nil { - kata.err = err continue } @@ -217,30 +209,25 @@ func (server *Server) handleDefinisi( } if len(res) == 0 { - return []byte(emptyResponse), nil + return []byte(jsonEmptyObject), nil } - resBody, err = res.pack() - if err != nil { - return nil, err - } - - return resBody, nil + return json.Marshal(res) } // // registerEndpoints register the API endpoints. // func (server *Server) registerEndpoints() (err error) { - epDefinisi := &http.Endpoint{ - Method: http.RequestMethodGet, + epDefinisi := &libhttp.Endpoint{ + Method: libhttp.RequestMethodGet, Path: pathAPIDefinisi, - RequestType: http.RequestTypeQuery, - ResponseType: http.ResponseTypeJSON, + RequestType: libhttp.RequestTypeQuery, + ResponseType: libhttp.ResponseTypeJSON, Call: server.handleDefinisi, } - err = server.http.RegisterEndpoint(epDefinisi) + err = server.httpd.RegisterEndpoint(epDefinisi) if err != nil { return fmt.Errorf("registerEndpoints %q: %w", pathAPIDefinisi, err) @@ -249,15 +236,15 @@ func (server *Server) registerEndpoints() (err error) { rand.Seed(time.Now().Unix()) pathAdmin := string(ascii.Random([]byte(ascii.LettersNumber), 16)) - epAdmin := &http.Endpoint{ - Method: http.RequestMethodGet, + epAdmin := &libhttp.Endpoint{ + Method: libhttp.RequestMethodGet, Path: pathAdmin, - RequestType: http.RequestTypeQuery, - ResponseType: http.ResponseTypeHTML, + RequestType: libhttp.RequestTypeQuery, + ResponseType: libhttp.ResponseTypeHTML, Call: server.handleAdmin, } - err = server.http.RegisterEndpoint(epAdmin) + err = server.httpd.RegisterEndpoint(epAdmin) if err != nil { return fmt.Errorf("registerEndpoints %q: %w", pathAdmin, err) } diff --git a/telegram_bot.go b/telegram_bot.go index 061bb4c..3892ece 100644 --- a/telegram_bot.go +++ b/telegram_bot.go @@ -1,8 +1,8 @@ -// Copyright 2020, Shulhan . All rights reserved. +// Copyright 2020, Shulhan . All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package kamusku +package kamusd import ( "bytes" @@ -11,6 +11,7 @@ import ( "os" "strings" + "git.sr.ht/~shulhan/kamusku" "github.com/shuLhan/share/api/telegram/bot" ) @@ -110,7 +111,7 @@ func (tgbot *TelegramBot) handleCommandDefinisi(update bot.Update) { daftarKata := strings.Split(msgReq.CommandArgs, ",") - def, err := tgbot.apiClient.CariDefinisi(daftarKata) + def, err := tgbot.apiClient.Lookup(daftarKata) if err != nil { tgbot.sendError(msgReq, "", err.Error()) return @@ -164,34 +165,33 @@ dan bebas dipakai oleh publik. } } -func formatText(definisiKata DefinisiResponse) string { +func formatText(wordDef kamusku.LookupResponse) string { buf := &bytes.Buffer{} - for k, kata := range definisiKata { + for k, kata := range wordDef { fmt.Fprintf(buf, "%s\n", k) - if len(kata.Pesan) > 0 { - fmt.Fprintln(buf, " "+kata.Pesan) + if len(kata.Message) > 0 { + fmt.Fprintln(buf, " "+kata.Message) fmt.Fprintln(buf, "") continue } - if len(kata.Dasar) > 0 { - fmt.Fprintf(buf, " Kata dasar: %s\n\n", - kata.Dasar) + if len(kata.Root) > 0 { + fmt.Fprintf(buf, " Kata dasar: %s\n\n", kata.Root) } - for x, def := range kata.Definisi { - fmt.Fprintf(buf, "‣ Definisi #%d: %s\n", x+1, def.Isi) + for x, def := range kata.Definition { + fmt.Fprintf(buf, "‣ Definisi #%d: %s\n", x+1, def.Value) - if len(def.Kelas) > 0 { + if len(def.Classes) > 0 { fmt.Fprintln(buf, " Kelas kata,") - for _, kelas := range def.Kelas { + for _, kelas := range def.Classes { fmt.Fprintln(buf, " • "+kelas) } } - if len(def.Contoh) > 0 { + if len(def.Examples) > 0 { fmt.Fprintln(buf, " Contoh,") - for _, contoh := range def.Contoh { + for _, contoh := range def.Examples { fmt.Fprintln(buf, " • "+contoh) } } diff --git a/testdata/entri.html b/testdata/entri.html deleted file mode 100644 index 1899950..0000000 --- a/testdata/entri.html +++ /dev/null @@ -1,408 +0,0 @@ - - - - - - - - Hasil Pencarian - KBBI Daring - - - - - - -
- - -
- -
-

- - Halo Shulhan! - Sudahkah Anda mengecek - halaman manajemen akun Anda? Anda - dapat melihat cara membukanya - di sini. Jika - Anda pernah mengajukan - usulan-usulan, - mungkin usulan-usulan tersebut telah diproses oleh redaksi - kami. -

-
-
- -
-
-
-
-
- - - - -
-
-
-

- -
- -
-

- in.for.ma.si - -

-

- ⇢ Tesaurus -

-
    -
  1. - n - penerangan -
  2. -
  3. - n - pemberitahuan; kabar atau berita tentang sesuatu -
  4. -
  5. - n - Ling - keseluruhan makna yang menunjang amanat yang terlihat - dalam bagian-bagian amanat itu -
  6. -
  7. - Usulkan makna baru -
  8. -
-

Kata Turunan

- -

Gabungan Kata

- -

-

- Usulkan entri baru -

-
- -
- - - - - - - - - - - diff --git a/testdata/entri_analisa.html b/testdata/entri_analisa.html deleted file mode 100644 index 9ba807d..0000000 --- a/testdata/entri_analisa.html +++ /dev/null @@ -1,342 +0,0 @@ - - - - - - - - Hasil Pencarian - KBBI Daring - - - - - - -
- - -
- -
-

- - Halo Shulhan! - Sudahkah Anda mengecek - halaman manajemen akun Anda? Anda - dapat melihat cara membukanya - di sini. Jika - Anda pernah mengajukan - usulan-usulan, - mungkin usulan-usulan tersebut telah diproses oleh redaksi - kami. -

-
-
- -
-
-
-
-
- - - - -
-
-
-

- -
- -
-

- ana.li.sa - -

-

- ⇢ Tesaurus -

- -

-

- Usulkan entri baru -

-
- -
- - - - - - - - - - - diff --git a/testdata/kbbi_dasar.html b/testdata/kbbi_dasar.html deleted file mode 100644 index 4bd5170..0000000 --- a/testdata/kbbi_dasar.html +++ /dev/null @@ -1,707 +0,0 @@ - - - - - - - - Jenis - KBBI Daring - - - - - - - -
- - - -
-

Daftar Entri Jenis Dasar

-
- -
- -
- - Halaman 1 / 501 - -
- -
- Hasil Pencarian: 1 - 100 dari 50001 -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- A - B - C - D - E - F - G - H - I - J - K - L - M - N - O - P - Q - R - S - T - U - V - W - X - Y - Z - Semua -
- -
- - - - -
- - -
- - - - - - - - - - - - - -- cgit v1.3