commit - edc1831cb3d3372a55ed8721a2ee5f1e854d6967
commit + d127bfea684b80ced83693d029c8a3b38e9e3546
blob - e9d1da12f19339f89a364299cb8ebe2152dde288
blob + b2da7b9ec6e57de642ba2dfa18436e27338b55e7
--- main.go
+++ main.go
_ "embed"
"encoding/json"
"find/utils"
+ "html"
"html/template"
"io"
"net/http"
Web []WebRes
News []NewsRes
Images []ImgRes
+ ImgRows [][]ImgRes
Count int
- ShowTabs, HasPrev, HasNext bool
+ ShowTabs, ShowNews bool
+ HasPrev, HasNext bool
Page, PrevPage, NextPage int
PrevNPT, PrevH, NextH template.URL
}
ramMu sync.RWMutex
ram = map[string]Page{}
cdir, base string
+ backend string // "4get" or "librey"
)
// --- helpers ---
return nil
}
+// --- librey backend ---
+
+type libItem struct {
+ Title, URL, Description string
+ Thumbnail, Alt string
+ Special *struct{ Response, Source, Image string } `json:"special_response"`
+}
+
+// LibreY's api.php returns a plain JSON array, except when PHP string keys
+// ("results_source", "error") are mixed in, in which case it becomes an
+// object with numeric string keys. Handle both.
+func libreyItems(body []byte) ([]libItem, string, error) {
+ var arr []libItem
+ if json.Unmarshal(body, &arr) == nil { return arr, "", nil }
+ var obj map[string]json.RawMessage
+ if err := json.Unmarshal(body, &obj); err != nil { return nil, "", err }
+ if e, ok := obj["error"]; ok {
+ var em struct{ Message string }
+ _ = json.Unmarshal(e, &em)
+ if em.Message == "" { em.Message = "backend error" }
+ return nil, em.Message, nil
+ }
+ var out []libItem
+ for i := 0; ; i++ {
+ raw, ok := obj[strconv.Itoa(i)]
+ if !ok { break }
+ var it libItem
+ if json.Unmarshal(raw, &it) == nil { out = append(out, it) }
+ }
+ return out, "", nil
+}
+
+func fetchLibrey(tab, q string, pg int, p *Page) error {
+ t := "0"
+ if tab == "images" { t = "1" }
+ ck := "librey" + t
+ pgs := strconv.Itoa(pg)
+ var body []byte; cached := false
+ if b, ok := diskGet(ck, q, pgs); ok {
+ body, cached = b, true
+ } else {
+ req, _ := http.NewRequest("GET", base+"/api.php?q="+esc(q)+"&t="+t+"&p="+strconv.Itoa(pg-1), nil)
+ req.Header.Set("Accept", "application/json")
+ resp, err := client.Do(req); if err != nil { return err }
+ body, _ = io.ReadAll(resp.Body); resp.Body.Close()
+ }
+ items, apiErr, err := libreyItems(body)
+ if err != nil { return err }
+ if apiErr != "" { p.Error = apiErr; return nil }
+ if !cached && len(items) > 0 { diskSet(ck, q, pgs, body) }
+
+ un := html.UnescapeString // librey htmlspecialchars()-escapes everything
+ for _, it := range items {
+ if it.Special != nil {
+ r, s := trim(un(it.Special.Response)), trim(it.Special.Source)
+ if r != "" {
+ var b strings.Builder
+ b.WriteString(template.HTMLEscapeString(r))
+ if s != "" {
+ b.WriteString(`<br><a href="` + template.HTMLEscapeString(s) + `">` + template.HTMLEscapeString(s) + `</a>`)
+ }
+ p.Answer = template.HTML(b.String())
+ }
+ continue
+ }
+ switch tab {
+ case "images":
+ ti, u, th := trim(un(it.Alt)), trim(un(it.URL)), trim(un(it.Thumbnail))
+ if u == "" && th == "" { continue }
+ if ti == "" { ti = u }
+ p.Images = append(p.Images, ImgRes{Title: ti, URL: u, Thumb: th})
+ default:
+ ti, u, d := trim(un(it.Title)), trim(un(it.URL)), trim(un(it.Description))
+ if ti == "" && u == "" { continue }
+ if ti == "" { ti = u }
+ p.Web = append(p.Web, WebRes{Title: ti, URL: u, Desc: d})
+ }
+ }
+ if tab == "images" { p.Count = len(p.Images) } else { p.Count = len(p.Web) }
+ p.HasNext = p.Count > 0
+ return nil
+}
+
+func chunkImgs(in []ImgRes, n int) [][]ImgRes {
+ var rows [][]ImgRes
+ for i := 0; i < len(in); i += n {
+ j := i + n; if j > len(in) { j = len(in) }
+ rows = append(rows, in[i:j])
+ }
+ return rows
+}
+
// --- handlers ---
func home(w http.ResponseWriter, r *http.Request) {
tab := trim(r.URL.Query().Get("tab"))
pg := atoi(r.URL.Query().Get("page"), 1)
if tab != "images" && tab != "news" { tab = "web" }
+ if backend == "librey" && tab == "news" { tab = "web" } // librey has no news
var conv utils.Conv
if amtStr := r.URL.Query().Get("amt"); amtStr != "" {
p := Page{
Tab: tab, Query: q,
EscQ: template.URL(esc(q)), EscNPT: template.URL(esc(npt)),
- ShowTabs: true, Page: pg, PrevPage: pg - 1, NextPage: pg + 1, HasPrev: pg > 1,
+ ShowTabs: true, ShowNews: backend != "librey",
+ Page: pg, PrevPage: pg - 1, NextPage: pg + 1, HasPrev: pg > 1,
Conv: conv, Calc: calc, Dict: dict,
}
if p.HasPrev && len(h) > 0 {
_ = tpl.ExecuteTemplate(w, "head", p)
if f, ok := w.(http.Flusher); ok { f.Flush() }
- if err := fetchParse(tab, q, npt, &p); err != nil { p.Error = err.Error() }
+ var ferr error
+ if backend == "librey" { ferr = fetchLibrey(tab, q, pg, &p) } else { ferr = fetchParse(tab, q, npt, &p) }
+ if ferr != nil { p.Error = ferr.Error() }
+ p.ImgRows = chunkImgs(p.Images, 4)
if p.HasNext {
nh := append(append([]string{}, h...), npt); if npt == "" { nh = h }
p.NextH = template.URL(esc(strings.Join(nh, ",")))
func main() {
cdir = env("CACHE_DIR", "/cache")
- base = strings.TrimRight(env("FOURGET_URL", "http://10.88.0.134"), "/")
+ backend = env("BACKEND", "4get") // "4get" or "librey"
+ def := "http://10.88.0.134"
+ if backend == "librey" { def = "http://127.0.0.1:8080" }
+ base = strings.TrimRight(env("BACKEND_URL", env("FOURGET_URL", def)), "/")
_ = os.MkdirAll(cdir, 0o755)
utils.ConvInit()
var b strings.Builder
- _ = tpl.ExecuteTemplate(&b, "head", Page{Tab: "web"})
+ _ = tpl.ExecuteTemplate(&b, "head", Page{Tab: "web", ShowNews: backend != "librey"})
_ = tpl.ExecuteTemplate(&b, "tail", Page{Tab: "web"})
homeHTML = []byte(b.String())
blob - a2d18a4578c6027b8664f021ed4f7cda7376427a
blob + 2b83a6fddcc6f37ef0bd4ee723b5b148520553de
--- utils/units.go
+++ utils/units.go
package utils
-// Unit catalog — conversion factors are relative to the base unit of each category.
-// Currency factors are unused (fetched live); temperature uses special-case math.
-
type udef struct{ id, label string; fac float64; aka string }
type cdef struct{ id, label string; units []udef }