Rohan Verma



Commit Diff


commit - edc1831cb3d3372a55ed8721a2ee5f1e854d6967
commit + d127bfea684b80ced83693d029c8a3b38e9e3546
blob - e9d1da12f19339f89a364299cb8ebe2152dde288
blob + b2da7b9ec6e57de642ba2dfa18436e27338b55e7
--- main.go
+++ main.go
@@ -4,6 +4,7 @@ import (
 	_ "embed"
 	"encoding/json"
 	"find/utils"
+	"html"
 	"html/template"
 	"io"
 	"net/http"
@@ -44,8 +45,10 @@ type Page struct {
 	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
 }
@@ -76,6 +79,7 @@ var (
 	ramMu      sync.RWMutex
 	ram        = map[string]Page{}
 	cdir, base string
+	backend    string // "4get" or "librey"
 )
 
 // --- helpers ---
@@ -233,6 +237,98 @@ func fetchParse(tab, q, npt string, p *Page) error {
 	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) {
@@ -249,6 +345,7 @@ func search(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 != "" {
@@ -265,7 +362,8 @@ func search(w http.ResponseWriter, r *http.Request) {
 	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 {
@@ -286,7 +384,10 @@ func search(w http.ResponseWriter, r *http.Request) {
 	_ = 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, ",")))
@@ -297,12 +398,15 @@ func search(w http.ResponseWriter, r *http.Request) {
 
 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
@@ -1,8 +1,5 @@
 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 }