diff options
| -rw-r--r-- | client.go | 40 | ||||
| -rw-r--r-- | cmd/kbbi/main.go | 74 | ||||
| -rw-r--r-- | direct_client.go | 120 | ||||
| -rw-r--r-- | direct_client_test.go | 2 | ||||
| -rw-r--r-- | server.go | 16 |
5 files changed, 137 insertions, 115 deletions
@@ -5,7 +5,7 @@ package kbbi import ( - "net/http" + "fmt" ) // @@ -19,19 +19,18 @@ type Client struct { // // NewClient create and initialize new client. -// If cookies is not empty, the direct client will be initialized and actived. // -func NewClient(cookies []*http.Cookie) (cl *Client, err error) { +func NewClient() (cl *Client, err error) { cl = &Client{} - cl.direct, err = newDirectClient(cookies) + cl.direct, err = newDirectClient() if err != nil { return nil, err } cl.api = newAPIClient("") - if cookies != nil { + if cl.IsAuthenticated() { cl.active = cl.direct } else { cl.active = cl.api @@ -59,6 +58,14 @@ func (cl *Client) CariDefinisi(words []string) ( } // +// IsAuthenticated will return true if client has logged in to KBBI official +// server. +// +func (cl *Client) IsAuthenticated() bool { + return cl.direct.isAuthenticated() +} + +// // ListKataDasar list all of the root words in dictionary. // func (cl *Client) ListKataDasar() (res DaftarKata, err error) { @@ -78,26 +85,13 @@ func (cl *Client) ListKataDasar() (res DaftarKata, err error) { // Login authenticate the client using username and password to official KBBI // server. // -func (cl *Client) Login(user, pass string) (cookies []*http.Cookie, err error) { - if cl.direct == nil { - cl.direct, err = newDirectClient(nil) - if err != nil { - return nil, err - } - } - cookies, err = cl.direct.login(user, pass) +func (cl *Client) Login(user, pass string) (err error) { + err = cl.direct.login(user, pass) if err != nil { - return nil, err + return fmt.Errorf("Login: %w", err) } + cl.active = cl.direct - return cookies, nil -} -// -// SetCookies for HTTP request in direct client. -// -func (cl *Client) SetCookies(cookies []*http.Cookie) { - if cl.direct != nil { - cl.direct.setCookies(cookies) - } + return nil } diff --git a/cmd/kbbi/main.go b/cmd/kbbi/main.go index adba621..3b07c36 100644 --- a/cmd/kbbi/main.go +++ b/cmd/kbbi/main.go @@ -2,19 +2,16 @@ // 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 ( - "bytes" - "encoding/gob" - "errors" "flag" "fmt" - "io/ioutil" "log" - "net/http" - "os" - "path/filepath" "sort" "github.com/shuLhan/kbbi" @@ -34,14 +31,6 @@ func main() { log.SetFlags(0) - baseDir, err := os.UserConfigDir() - if err != nil { - log.Fatal("kbbi.createConfigDir:", err) - } - - createConfigDir(baseDir) - cookies := loadCookies(baseDir) - flag.StringVar(&surel, "surel", "", "Nama pengguna") flag.StringVar(&sandi, "sandi", "", "Sandi pengguna") flag.BoolVar(&isListKataDasar, "daftar-kata-dasar", false, @@ -49,25 +38,22 @@ func main() { flag.Parse() - cl, err := kbbi.NewClient(cookies) + cl, err := kbbi.NewClient() if err != nil { log.Fatal(err) } if len(surel) > 0 && len(sandi) > 0 { - cookies, err = cl.Login(surel, sandi) + err = cl.Login(surel, sandi) if err != nil { log.Fatal("kbbi: ", err) } - saveCookies(baseDir, cookies) - cl.SetCookies(cookies) } if isListKataDasar { - if len(cookies) == 0 { + if cl.IsAuthenticated() { log.Fatal("opsi -daftar-kata-dasar membutuhkan opsi -surel dan -sandi") } - listKataDasar(cl) return } @@ -106,13 +92,6 @@ func main() { } } -func createConfigDir(baseDir string) { - err := os.MkdirAll(filepath.Join(baseDir, configDir), 0700) - if err != nil { - log.Fatal("kbbi.createConfigDir:", err) - } -} - func listKataDasar(cl *kbbi.Client) { kataDasar, err := cl.ListKataDasar() if err != nil { @@ -131,42 +110,3 @@ func listKataDasar(cl *kbbi.Client) { fmt.Println(kata) } } - -func loadCookies(baseDir string) (cookies []*http.Cookie) { - f := filepath.Join(baseDir, configDir, cookieFile) - - _, err := os.Stat(f) - if errors.Is(err, os.ErrNotExist) { - return nil - } - - body, err := ioutil.ReadFile(f) - if err != nil { - log.Fatal("kbbi.loadCookies: ", err) - } - - dec := gob.NewDecoder(bytes.NewReader(body)) - - err = dec.Decode(&cookies) - if err != nil { - log.Println("kbbi.loadCookies: ", err) - } - - return cookies -} - -func saveCookies(baseDir string, cookies []*http.Cookie) { - f := filepath.Join(baseDir, configDir, cookieFile) - - var buf bytes.Buffer - enc := gob.NewEncoder(&buf) - err := enc.Encode(cookies) - if err != nil { - log.Fatal("kbbi.saveCookies: ", err) - } - - err = ioutil.WriteFile(f, buf.Bytes(), 0600) - if err != nil { - log.Fatal("kbbi.saveCookies: ", err) - } -} diff --git a/direct_client.go b/direct_client.go index 75f1404..aa296b5 100644 --- a/direct_client.go +++ b/direct_client.go @@ -6,12 +6,16 @@ package kbbi import ( "bytes" + "encoding/gob" + "errors" "fmt" "io/ioutil" "log" "net/http" "net/http/cookiejar" "net/url" + "os" + "path/filepath" "strconv" "strings" @@ -21,6 +25,8 @@ import ( ) const ( + cookieFile = "cookie" + configDir = "kbbi" maxPageNumber = 501 ) @@ -28,7 +34,9 @@ const ( // directClient for KBBI web using HTTP. // type directClient struct { + baseDir string cookieURL *url.URL + cookies []*http.Cookie httpc *http.Client } @@ -36,7 +44,7 @@ type directClient struct { // newDirectClient create and initialize new client that connect directly to // KBBI official website. // -func newDirectClient(cookies []*http.Cookie) (cl *directClient, err error) { +func newDirectClient() (cl *directClient, err error) { cookieURL, err := url.Parse(baseURL) if err != nil { return nil, fmt.Errorf("newDirectClient: %w", err) @@ -48,11 +56,7 @@ func newDirectClient(cookies []*http.Cookie) (cl *directClient, err error) { jar, err := cookiejar.New(jarOpt) if err != nil { - return nil, err - } - - if cookies != nil { - jar.SetCookies(cookieURL, cookies) + return nil, fmt.Errorf("newDirectClient: %w", err) } cl = &directClient{ @@ -63,6 +67,15 @@ func newDirectClient(cookies []*http.Cookie) (cl *directClient, err error) { }, } + 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 } @@ -169,20 +182,26 @@ func (cl *directClient) ListKataDasar() (kataDasar DaftarKata, err error) { } // -// Login authenticate the client using username and password. +// isAuthenticated will return true if the client already login; otherwise it +// will return false. // -func (cl *directClient) login(user, pass string) ( - cookies []*http.Cookie, err error, -) { +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 nil, fmt.Errorf("Login: %w", err) + return fmt.Errorf("Login: %w", err) } params := url.Values{ paramNameRequestVerificationToken: []string{tokenLogin}, - paramNamePosel: []string{user}, - paramNameKataSandi: []string{pass}, + paramNamePosel: []string{surel}, + paramNameKataSandi: []string{sandi}, paramNameIngatSaya: []string{paramValueFalse}, } @@ -190,39 +209,39 @@ func (cl *directClient) login(user, pass string) ( req, err := http.NewRequest(http.MethodPost, loginURL, reqBody) if err != nil { - return nil, fmt.Errorf("Login: %w", err) + return fmt.Errorf("Login: %w", err) } req.Header.Set(headerNameContentType, headerValueContentType) res, err := cl.httpc.Do(req) if err != nil { - return nil, fmt.Errorf("Login: %w", err) + return fmt.Errorf("Login: %w", err) } defer res.Body.Close() resBody, err := ioutil.ReadAll(res.Body) if err != nil { - return nil, fmt.Errorf("Login: %w", err) + return fmt.Errorf("Login: %w", err) } if res.StatusCode >= http.StatusBadRequest { - return nil, fmt.Errorf("Login: %d %s", res.StatusCode, resBody) + return fmt.Errorf("login: %d %s", res.StatusCode, resBody) } - cookies = cl.httpc.Jar.Cookies(cl.cookieURL) + cl.cookies = cl.httpc.Jar.Cookies(cl.cookieURL) + cl.setCookies() + cl.saveCookies() - return cookies, nil + return nil } // // setCookies for HTTP request that need an authentication. // -func (cl *directClient) setCookies(cookies []*http.Cookie) { - if len(cookies) > 0 { - cl.httpc.Jar.SetCookies(cl.cookieURL, cookies) - } +func (cl *directClient) setCookies() { + cl.httpc.Jar.SetCookies(cl.cookieURL, cl.cookies) } func (cl *directClient) parseHTMLKataDasar(htmlBody []byte) ( @@ -309,3 +328,58 @@ func (cl *directClient) preLogin() (token string, err error) { 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 index 894b876..dd3b59b 100644 --- a/direct_client_test.go +++ b/direct_client_test.go @@ -15,7 +15,7 @@ func TestDirectClient_parseHTMLKataDasar(t *testing.T) { t.Fatal(err) } - cl, err := newDirectClient(nil) + cl, err := newDirectClient() if err != nil { t.Fatal(err) } @@ -8,6 +8,7 @@ import ( "fmt" "log" stdhttp "net/http" + "os" "strings" "sync" "time" @@ -17,6 +18,8 @@ import ( ) const ( + envKbbiSandi = "KBBI_SANDI" + envKbbiSurel = "KBBI_SUREL" defRootDir = "_www-kbbi" defListen = ":3394" emptyResponse = "{}" @@ -68,7 +71,7 @@ func NewServer(kamusStorage string) (server *Server, err error) { return nil, fmt.Errorf("http.NewServer: %w", err) } - server.forwardc, err = newDirectClient(nil) + server.forwardc, err = newDirectClient() if err != nil { return nil, fmt.Errorf("http.NewServer: %w", err) } @@ -78,6 +81,17 @@ func NewServer(kamusStorage string) (server *Server, err error) { return nil, fmt.Errorf("http.NewServer: %w", err) } + if !server.forwardc.isAuthenticated() { + surel := os.Getenv(envKbbiSurel) + sandi := os.Getenv(envKbbiSandi) + if len(surel) > 0 && len(sandi) > 0 { + err = server.forwardc.login(surel, sandi) + if err != nil { + return nil, err + } + } + } + return server, nil } |
