package main import ( "net/http" "strings" "time" ) type UserSettings struct { Theme string SiteLanguage string SearchLanguage string SafeSearch string IsThemeDark bool } func loadUserSettings(w http.ResponseWriter, r *http.Request) UserSettings { var settings UserSettings saveRequired := false for _, cd := range AllCookies { // Attempt to read the cookie if cookie, err := r.Cookie(cd.Name); err == nil { // Use SetValue to update the correct UserSettings field cd.SetValue(&settings, cookie.Value) } else { // If cookie is missing and you want a default value, set it here switch cd.Name { case "theme": // Default theme to "dark" if missing cd.SetValue(&settings, "dark") saveRequired = true case "site_language": // Fallback to Accept-Language or "en" acceptLang := r.Header.Get("Accept-Language") if acceptLang != "" { cd.SetValue(&settings, normalizeLangCode(acceptLang)) } else { cd.SetValue(&settings, "en") } saveRequired = true case "safe": // Default safe to "" cd.SetValue(&settings, "") saveRequired = true // etc. for other cookies if needed } } } // If theme was set, update IsThemeDark just to be sure // Alternatively do it inside SetValue for "theme" settings.IsThemeDark = settings.Theme == "dark" || settings.Theme == "night" || settings.Theme == "black" || settings.Theme == "latte" // Save any new default cookies that might have been triggered if saveRequired { saveUserSettings(w, settings) } return settings } func saveUserSettings(w http.ResponseWriter, settings UserSettings) { expiration := time.Now().Add(90 * 24 * time.Hour) for _, cd := range AllCookies { http.SetCookie(w, &http.Cookie{ Name: cd.Name, Value: cd.GetValue(settings), Path: "/", Expires: expiration, Secure: true, // Ensure HTTPS is required HttpOnly: true, SameSite: http.SameSiteStrictMode, // Restrict cross-site usage }) } printDebug("settings saved: %v", settings) } func handleSaveSettings(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { // Load current settings settings := loadUserSettings(w, r) // Update only the settings that were submitted in the form if theme := r.FormValue("theme"); theme != "" { settings.Theme = theme } // Update site language if provided if siteLang := r.FormValue("site_lang"); siteLang != "" { settings.SiteLanguage = siteLang } else { // If site_lang is empty, try to get from Accept-Language header acceptLang := r.Header.Get("Accept-Language") if acceptLang != "" { settings.SiteLanguage = strings.Split(acceptLang, ",")[0] } } // Update search language if provided if searchLang := r.FormValue("search_lang"); searchLang != "" { settings.SearchLanguage = searchLang } // Update safe search if provided if safe := r.FormValue("safe"); safe != "" { settings.SafeSearch = safe } // Save the updated settings saveUserSettings(w, settings) // Redirect back to the previous page or settings page http.Redirect(w, r, r.FormValue("past"), http.StatusSeeOther) } } func handleSettings(w http.ResponseWriter, r *http.Request) { // Load user settings settings := loadUserSettings(w, r) // Prepare data with user settings and icon paths as a map data := map[string]interface{}{ "LanguageOptions": languageOptions, "CurrentSiteLang": settings.SiteLanguage, "CurrentSearchLang": settings.SearchLanguage, "Theme": settings.Theme, "Safe": settings.SafeSearch, "IsThemeDark": settings.IsThemeDark, } printDebug("Rendering settings with data: %+v", data) // Use renderTemplate to include the icons renderTemplate(w, "settings.html", data) } // Helper function to normalize language codes func normalizeLangCode(lang string) string { lang = strings.ToLower(lang) // First, check if the language code is already valid if isValidLangCode(lang) { return lang } // Strip regional codes (e.g., en-US -> en) if strings.Contains(lang, "-") { lang = strings.Split(lang, "-")[0] } // Re-check if the normalized version is valid if isValidLangCode(lang) { return lang } // If the language is not recognized, default to "en" return "en" } // Helper function to check if a language code exists in the language options func isValidLangCode(lang string) bool { for _, opt := range languageOptions { if opt.Code == lang { return true } } return false } // CookieDefinition describes how a single cookie is handled type CookieDefinition struct { Name string // GetValue extracts the corresponding field from UserSettings GetValue func(UserSettings) string // SetValue updates the corresponding field in UserSettings SetValue func(*UserSettings, string) // Description used in privacy table or docs Description string } // AllCookies defines every cookie we handle in a single slice. // Add or remove entries here, and the rest updates automatically. var AllCookies = []CookieDefinition{ { Name: "theme", Description: "Stores the selected theme (dark, light, etc.)", GetValue: func(s UserSettings) string { return s.Theme }, SetValue: func(s *UserSettings, val string) { s.Theme = val s.IsThemeDark = (val == "dark" || val == "night" || val == "black" || val == "latte") }, }, { Name: "site_language", Description: "Stores the preferred site language.", GetValue: func(s UserSettings) string { return s.SiteLanguage }, SetValue: func(s *UserSettings, val string) { s.SiteLanguage = val }, }, { Name: "search_language", Description: "Stores the preferred language for search results.", GetValue: func(s UserSettings) string { return s.SearchLanguage }, SetValue: func(s *UserSettings, val string) { s.SearchLanguage = val }, }, { Name: "safe", Description: "Stores the Safe Search setting.", GetValue: func(s UserSettings) string { return s.SafeSearch }, SetValue: func(s *UserSettings, val string) { s.SafeSearch = val }, }, } type CookieRow struct { Name string Value string Description string Expiration string } func generateCookieTable(r *http.Request) []CookieRow { var rows []CookieRow for _, cd := range AllCookies { value := "[Not Set]" if cookie, err := r.Cookie(cd.Name); err == nil { value = cookie.Value } rows = append(rows, CookieRow{ Name: cd.Name, Value: value, Description: cd.Description, Expiration: "90 days", }) } return rows }