package main import ( "crypto/rand" "encoding/base64" "encoding/json" "fmt" "html/template" mathrand "math/rand" "net/http" "net/url" "strings" "time" ) var ( funcs = template.FuncMap{ "sub": func(a, b int) int { return a - b }, "add": func(a, b int) int { return a + b }, "translate": Translate, "toJSON": func(v interface{}) (string, error) { jsonBytes, err := json.Marshal(v) if err != nil { return "", err } return string(jsonBytes), nil }, "formatShortDate": func(date string) string { t, _ := time.Parse("2006-01-02", date) // return t.Format("Mon") // e.g. "Sat" return t.Format("2.1.") // e.g. "29.6." }, "weatherIcon": func(cur interface{}) string { switch c := cur.(type) { case map[string]interface{}: if cond, ok := c["Condition"].(string); ok { return iconForCond(cond) } case WeatherCurrent: return iconForCond(c.Condition) case *WeatherCurrent: return iconForCond(c.Condition) } return "๐ŸŒˆ" }, } ) func iconForCond(cond string) string { switch cond { case "Clear": return "โ˜€๏ธ" case "Partly cloudy": return "โ›…" case "Cloudy": return "โ˜๏ธ" case "Rain": return "๐ŸŒง๏ธ" case "Snow": return "โ„๏ธ" case "Thunderstorm": return "โ›ˆ๏ธ" case "Fog": return "๐ŸŒซ๏ธ" default: return "๐ŸŒˆ" } } type SearchEngine struct { Name string Func func(string, string, string, int) ([]SearchResult, time.Duration, error) } type LinkParts struct { Domain template.HTML Path template.HTML RootURL string // used by getFaviconProxyURL() } // Helper function to render templates without elapsed time measurement func renderTemplate(w http.ResponseWriter, tmplName string, data map[string]interface{}) { // Generate icon paths for SVG and PNG, including a 1/10 chance for an alternate icon iconPathSVG, iconPathPNG := GetIconPath() // Add icon paths to data map so they are available in all templates if data == nil { data = make(map[string]interface{}) } data["IconPathSVG"] = iconPathSVG data["IconPathPNG"] = iconPathPNG // Parse and execute the template with shared functions tmpl, err := template.New(tmplName).Funcs(funcs).ParseFiles("templates/" + tmplName) if err != nil { printErr("Error parsing template: %v", err) http.Error(w, Translate("internal_server_error"), http.StatusInternalServerError) return } // Execute the template err = tmpl.Execute(w, data) if err != nil { printErr("Error executing template: %v", err) http.Error(w, Translate("internal_server_error"), http.StatusInternalServerError) } } // Randoms string generator used for auth code func generateStrongRandomString(length int) string { bytes := make([]byte, length) _, err := rand.Read(bytes) if err != nil { printErr("Error generating random string: %v", err) } return base64.URLEncoding.EncodeToString(bytes)[:length] } // Checks if the URL already includes a protocol func hasProtocol(url string) bool { return strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") } // Checks if the domain is a local address func isLocalAddress(domain string) bool { return domain == "localhost" || strings.HasPrefix(domain, "127.") || strings.HasPrefix(domain, "192.168.") || strings.HasPrefix(domain, "10.") } // Ensures that HTTP or HTTPS is before the address if needed func addProtocol(domain string) string { if hasProtocol(domain) { return domain } if isLocalAddress(domain) { return "http://" + domain } return "https://" + domain } // GetIconPath returns both SVG and PNG icon paths, with a 1/10 chance for a randomly generated "alt" icon. func GetIconPath() (string, string) { // 1 in 10 chance to select an alt icon if mathrand.Intn(10) == 0 { // Generate a random number between 2 and 4 altIconNumber := 2 + mathrand.Intn(3) // mathrand.Intn(3) generates 0, 1, or 2 selectedAltIcon := "icon-alt" + fmt.Sprint(altIconNumber) return "/static/images/" + selectedAltIcon + ".svg", "/static/images/" + selectedAltIcon + ".png" } // Default paths return "/static/images/icon.svg", "/static/images/icon.png" } // FormatElapsedTime formats elapsed time as a string, // using: // - "> 0.01 ms" if under 49ยตs // - "0.xx ms" if under 1ms // - "xxx ms" if under 300ms // - "x.xx seconds" otherwise func FormatElapsedTime(elapsed time.Duration) string { if elapsed < 49*time.Microsecond { return fmt.Sprintf("> 0.01 %s", Translate("milliseconds")) } else if elapsed < time.Millisecond { ms := float64(elapsed.Microseconds()) / 1000.0 return fmt.Sprintf("%.2f %s", ms, Translate("milliseconds")) } else if elapsed < 300*time.Millisecond { return fmt.Sprintf("%d %s", elapsed.Milliseconds(), Translate("milliseconds")) } return fmt.Sprintf("%.2f %s", elapsed.Seconds(), Translate("seconds")) } func FormatURLParts(rawURL string) (domain, path, rootURL string) { parsed, err := url.Parse(rawURL) if err != nil || parsed.Host == "" { return "", "", "" } domain = parsed.Host if strings.HasPrefix(domain, "www.") { domain = domain[4:] } rootURL = parsed.Scheme + "://" + parsed.Host path = strings.Trim(parsed.Path, "/") pathSegments := strings.Split(path, "/") var cleanSegments []string for _, seg := range pathSegments { if seg != "" { cleanSegments = append(cleanSegments, seg) } } path = strings.Join(cleanSegments, "/") return domain, path, rootURL } func FormatLinkHTML(rawURL string) LinkParts { domain, path, root := FormatURLParts(rawURL) lp := LinkParts{ RootURL: root, } lp.Domain = template.HTML(fmt.Sprintf(`%s`, template.HTMLEscapeString(domain))) if path != "" { pathDisplay := strings.ReplaceAll(path, "/", " โ€บ ") lp.Path = template.HTML(fmt.Sprintf(` โ€บ %s`, template.HTMLEscapeString(pathDisplay))) } return lp } // Converts any struct to a map[string]interface{} using JSON round-trip. // Useful for rendering templates with generic map input. func toMap(data interface{}) map[string]interface{} { jsonBytes, _ := json.Marshal(data) var result map[string]interface{} _ = json.Unmarshal(jsonBytes, &result) return result }