package jisho
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"chirami/internal/dict"
)
// BaseURL is the Jisho API endpoint for words search. It is exported so tests can
// point to an httptest server.
var BaseURL = "https://jisho.org/api/v1/search/words"
type apiItem struct {
IsCommon bool `json:"is_common"`
Tags []string `json:"tags"`
JLPT []string `json:"jlpt"`
Japanese []struct {
Word string `json:"word"`
Reading string `json:"reading"`
} `json:"japanese"`
Senses []struct {
EnglishDefinitions []string `json:"english_definitions"`
PartsOfSpeech []string `json:"parts_of_speech"`
Tags []string `json:"tags"`
Info []string `json:"info"`
} `json:"senses"`
}
type apiResponse struct {
Data []apiItem `json:"data"`
}
func parseJLPT(tags []string) string {
for _, t := range tags {
lower := strings.ToLower(t)
if strings.HasPrefix(lower, "jlpt-n") {
return strings.ToUpper(strings.TrimPrefix(lower, "jlpt-"))
}
}
return ""
}
// Search searches jisho.org for word, returning matching dictionary entries.
func Search(ctx context.Context, word string) ([]dict.Entry, error) {
ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
defer cancel()
reqURL := fmt.Sprintf("%s?keyword=%s", BaseURL, url.QueryEscape(word))
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", "chirami/0.1")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("jisho: HTTP %d", resp.StatusCode)
}
var data apiResponse
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return nil, err
}
entries := make([]dict.Entry, 0, len(data.Data))
for _, item := range data.Data {
var kanji []dict.Form
var kana []dict.Form
seenKanji := make(map[string]bool)
seenKana := make(map[string]bool)
for _, j := range item.Japanese {
if j.Word != "" && !seenKanji[j.Word] {
seenKanji[j.Word] = true
kanji = append(kanji, dict.Form{Text: j.Word})
}
if j.Reading != "" && !seenKana[j.Reading] {
seenKana[j.Reading] = true
kana = append(kana, dict.Form{Text: j.Reading})
}
}
if item.IsCommon {
if len(kanji) > 0 {
kanji[0].Common = true
}
if len(kana) > 0 {
kana[0].Common = true
}
}
senses := make([]dict.Sense, 0, len(item.Senses))
for _, s := range item.Senses {
senses = append(senses, dict.Sense{
POS: s.PartsOfSpeech,
Misc: s.Tags,
Info: s.Info,
Gloss: s.EnglishDefinitions,
})
}
entries = append(entries, dict.Entry{
ID: 0,
Kanji: kanji,
Kana: kana,
Senses: senses,
JLPT: parseJLPT(item.JLPT),
})
}
return entries, nil
}