From 5ae97da6d086556a86bcaca4a36885735c9fd58b Mon Sep 17 00:00:00 2001 From: partisan Date: Sun, 5 Jan 2025 19:23:53 +0100 Subject: [PATCH] added privacy policy page and about section, improved dir check, fixed crash when idexer is disabled --- crawler.go | 5 +- indexer.go | 26 +++-- init.go | 18 ++- main.go | 45 ++++++++ static/css/style-imageviewer.css | 18 +-- static/css/style-menu.css | 104 +++++++++++++++++ static/css/style-privacy.css | 95 ++++++++++++++++ static/js/imageviewer.js | 2 +- static/js/minimenu.js | 9 ++ templates/files.html | 23 +++- templates/forums.html | 23 +++- templates/images.html | 23 +++- templates/map.html | 23 +++- templates/privacy.html | 133 ++++++++++++++++++++++ templates/search.html | 23 +++- templates/text.html | 23 +++- templates/videos.html | 23 +++- user-settings.go | 189 ++++++++++++++++++++----------- 18 files changed, 698 insertions(+), 107 deletions(-) create mode 100644 static/css/style-privacy.css create mode 100644 templates/privacy.html diff --git a/crawler.go b/crawler.go index afa7f9e..8caa073 100644 --- a/crawler.go +++ b/crawler.go @@ -14,8 +14,7 @@ var visitedStore *VisitedStore // webCrawlerInit is called during init on program start func webCrawlerInit() { - // Initialize the store with, say, batchSize=50 - store, err := NewVisitedStore(filepath.Join(config.DriveCache.Path, "visited-urls.txt"), 50) + store, err := NewVisitedStore(filepath.Join(config.DriveCache.Path, "visited-urls.txt"), config.IndexBatchSize) if err != nil { printErr("Failed to initialize visited store: %v", err) } @@ -170,7 +169,7 @@ func crawlDomainsToFile(domains [][2]string, maxPages int) error { userAgent, _ := GetUserAgent("crawler-chrome") title, desc, keywords := fetchPageMetadataChrome(fullURL, userAgent) if title == "" || desc == "" { - printWarn("Skipping %s: unable to get title/desc data", fullURL) + printDebug("Skipping %s: unable to get title/desc data", fullURL) // Here is print for all domains that fail to be crawled continue } diff --git a/indexer.go b/indexer.go index 73ca9e3..c8cf6fe 100644 --- a/indexer.go +++ b/indexer.go @@ -9,7 +9,6 @@ import ( "strconv" "strings" "sync" - "time" "github.com/blevesearch/bleve/v2" "golang.org/x/net/publicsuffix" @@ -73,17 +72,17 @@ func indexDocImmediately(link, title, tags, desc, rank string) error { return nil } -// StartBatchIndexing spawns a goroutine that flushes the buffer every interval. -func StartBatchIndexing() { - go func() { - ticker := time.NewTicker(config.IndexRefreshInterval) - defer ticker.Stop() +// // StartBatchIndexing spawns a goroutine that flushes the buffer every interval. +// func StartBatchIndexing() { +// go func() { +// ticker := time.NewTicker(config.IndexRefreshInterval) +// defer ticker.Stop() - for range ticker.C { - flushDocBuffer() - } - }() -} +// for range ticker.C { +// flushDocBuffer() +// } +// }() +// } func flushDocBuffer() { docBufferMu.Lock() @@ -264,6 +263,11 @@ func IndexFile(filePath string) error { // SearchIndex performs a full-text search on the indexed data. func SearchIndex(queryStr string, page, pageSize int) ([]Document, error) { + // Check if the indexer is enabled + if !config.IndexerEnabled { + return nil, fmt.Errorf("indexer is disabled") + } + exactMatch := bleve.NewMatchQuery(queryStr) // Exact match fuzzyMatch := bleve.NewFuzzyQuery(queryStr) // Fuzzy match fuzzyMatch.Fuzziness = 2 diff --git a/init.go b/init.go index 666d93a..bf0d220 100644 --- a/init.go +++ b/init.go @@ -3,6 +3,7 @@ package main import ( "flag" "os" + "path/filepath" ) var config Config @@ -77,9 +78,18 @@ func main() { // Check if the cache directory exists when caching is enabled if config.DriveCacheEnabled { - if _, err := os.Stat(config.DriveCache.Path); os.IsNotExist(err) { - printErr("Error: Drive cache is enabled, but cache directory '%s' does not exist.\n", config.DriveCache.Path) - os.Exit(1) // Exit with a non-zero status to indicate an error + cacheDir := config.DriveCache.Path + imagesDir := filepath.Join(cacheDir, "images") + + // Check if the directory already exists + if _, err := os.Stat(imagesDir); os.IsNotExist(err) { + // Try to create the directory since it doesn't exist + if err := os.MkdirAll(imagesDir, os.ModePerm); err != nil { + printErr("Error: Failed to create cache or images directory '%s': %v", imagesDir, err) + os.Exit(1) // Exit with a non-zero status to indicate an error + } + // Print a warning if the directory had to be created + printWarn("Warning: Created missing directory '%s'.", imagesDir) } } @@ -109,7 +119,7 @@ func main() { err := InitIndex() if err != nil { - printErr("Failed to initialize index:", err) + printErr("Failed to initialize index: %v", err) } webCrawlerInit() diff --git a/main.go b/main.go index cc6b8c3..12c2381 100755 --- a/main.go +++ b/main.go @@ -221,6 +221,7 @@ func runServer() { http.HandleFunc("/save-settings", handleSaveSettings) http.HandleFunc("/image/", handleImageServe) http.HandleFunc("/image_status", handleImageStatus) + http.HandleFunc("/privacy", handlePrivacyPage) http.HandleFunc("/opensearch.xml", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/opensearchdescription+xml") http.ServeFile(w, r, "static/opensearch.xml") @@ -235,6 +236,7 @@ func runServer() { http.HandleFunc("/save-settings", handleWebsiteDisabled) http.HandleFunc("/image/", handleWebsiteDisabled) http.HandleFunc("/image_status", handleWebsiteDisabled) + http.HandleFunc("/privacy", handleWebsiteDisabled) http.HandleFunc("/opensearch.xml", handleWebsiteDisabled) printInfo("Website functionality disabled.") } @@ -252,3 +254,46 @@ func handleWebsiteDisabled(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusServiceUnavailable) _, _ = w.Write([]byte("The website functionality is currently disabled.")) } + +func handlePrivacyPage(w http.ResponseWriter, r *http.Request) { + settings := loadUserSettings(w, r) + iconPathSVG, iconPathPNG := GetIconPath() + + // Define the data structure for the template + data := struct { + Theme string + IconPathSVG string + IconPathPNG string + IsThemeDark bool + CookieRows []CookieRow + CurrentLang string + Safe string + LanguageOptions []LanguageOption + }{ + Theme: settings.Theme, + IconPathSVG: iconPathSVG, + IconPathPNG: iconPathPNG, + IsThemeDark: settings.IsThemeDark, + CookieRows: generateCookieTable(r), + CurrentLang: settings.SiteLanguage, + Safe: settings.SafeSearch, + LanguageOptions: languageOptions, + } + + // Parse the template + tmpl, err := template.New("privacy.html").ParseFiles("templates/privacy.html") + if err != nil { + log.Printf("Error parsing template: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + // Set the response content type + w.Header().Set("Content-Type", "text/html; charset=utf-8") + + // Execute the template + if err := tmpl.Execute(w, data); err != nil { + log.Printf("Error executing template: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } +} diff --git a/static/css/style-imageviewer.css b/static/css/style-imageviewer.css index 4c0696f..ac6874a 100644 --- a/static/css/style-imageviewer.css +++ b/static/css/style-imageviewer.css @@ -60,13 +60,6 @@ gap: 5px; /* Add spacing between buttons */ } -.image-view-close .btn-nostyle { - background-color: inherit; - border: none; - padding: 0px; - cursor: pointer; -} - #viewer-close-button, #viewer-prev-button, #viewer-next-button { @@ -128,6 +121,7 @@ .full-size:hover, .proxy-size:hover { + transition: all 0.3s ease; text-decoration: underline; } @@ -136,15 +130,6 @@ visibility: visible; } -/* Button No Style */ -.btn-nostyle { - background-color: inherit; - border: none; - padding: 0px; - width: fit-content; - cursor: pointer; -} - /* Image Navigation Icons */ .image-close, .image-next, @@ -163,6 +148,7 @@ .image-close:hover, .image-next:hover, .image-before:hover { + transition: all 0.3s ease; background-color: var(--image-select); } diff --git a/static/css/style-menu.css b/static/css/style-menu.css index d85810b..95be6cf 100644 --- a/static/css/style-menu.css +++ b/static/css/style-menu.css @@ -1,3 +1,5 @@ +/* ------------------ Mini-Menu Styles ------------------ */ + .settings-search-div-search { right: 20px; top: 25px; @@ -140,4 +142,106 @@ margin-right: 0; border-radius: 0; } +} + +/* ------------------ About QGato Modal Styles ------------------ */ + +#aboutQGatoModal { + display: none; + position: fixed; + /* Center modal */ + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + + /* Keep it on top */ + z-index: 999; + + /* Match mini-menu background style */ + background-color: var(--html-bg); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); + border: 1px solid var(--border); + border-radius: 12px; + + /* Spacing & sizing */ + padding: 32px; + max-width: 600px; /* Increased width */ + max-height: 80vh; /* Optional: restrict height to 80% of viewport */ + overflow-y: auto; /* Enable scrolling if content exceeds height */ + color: var(--font-fg); +} + +#aboutQGatoModal #close-button { + position: absolute; + top: 12px; + right: 12px; /* Moved close button to top-right */ +} + +#aboutQGatoModal .modal-content { + text-align: center; + margin-top: 20px; /* Adjusted spacing */ +} + +/* Logo */ +#aboutQGatoModal .modal-content img { + width: 100px; /* Increased logo size */ + margin-bottom: 16px; +} + +/* Headings, paragraphs, etc. */ +#aboutQGatoModal .modal-content h2 { + font-size: 2rem; /* Larger heading */ + margin: 8px 0; +} + +#aboutQGatoModal .modal-content p { + font-size: 1.1rem; /* Larger paragraph text */ + margin: 12px 0; +} + +/* Container for the Source Code / Privacy Policy buttons */ +#aboutQGatoModal .button-container { + margin-top: 16px; + display: flex; + justify-content: center; + gap: 16px; +} + +/* Match mini-menu button style as closely as possible */ +#aboutQGatoModal .button-container button { + background-color: var(--button); + color: var(--font-fg); + border: 1px solid var(--border); + border-radius: 6px; + padding: 12px 16px; /* Larger button padding */ + font-size: 1rem; /* Larger button text */ + cursor: pointer; + transition: border 0.3s ease, background-color 0.3s ease, color 0.3s ease; +} + +#aboutQGatoModal .button-container button:hover { + border: 1px solid var(--font-fg); +} + +/* Close Button Style */ +.cloase-btn { + font-size: 1.5rem; /* Larger close button */ + color: var(--search-button); + border-radius: 50%; + padding: 8px; +} + +.cloase-btn:hover { + transition: all 0.3s ease; + background-color: var(--image-select); +} + +/* ------------------ Common Button No Style ------------------ */ + +.btn-nostyle { + background-color: inherit; + border: none; + padding: 0px; + width: fit-content; + cursor: pointer; } \ No newline at end of file diff --git a/static/css/style-privacy.css b/static/css/style-privacy.css new file mode 100644 index 0000000..5cfef4b --- /dev/null +++ b/static/css/style-privacy.css @@ -0,0 +1,95 @@ +/* Main content wrapper */ +.privacy-content-wrapper { + max-width: 800px; + margin: 80px auto 40px auto; + padding: 0 20px; +} + +/* Header section */ +.privacy-header { + text-align: center; + margin-bottom: 30px; +} + +.privacy-header h1 { + font-size: 2rem; + margin: 0; + color: var(--font-fg); +} + +.privacy-header p { + color: var(--fg); + margin-top: 10px; + font-size: 1.1rem; +} + +/* Section headings */ +.privacy-section h2 { + font-size: 1.5rem; + margin-bottom: 8px; + color: var(--font-fg); + border-bottom: 1px solid var(--border); + padding-bottom: 4px; +} + +/* Section text */ +.privacy-section p { + font-size: 1rem; + line-height: 1.6; + margin-bottom: 20px; + color: var(--fg); +} + +/* Footer */ +.privacy-footer { + text-align: center; + padding: 10px 0; + border-top: 1px solid var(--border); + color: var(--fg); + background-color: var(--html-bg); +} + +/* Links */ +.privacy-section a { + color: var(--link); + text-decoration: none; +} + +.privacy-section a:hover { + text-decoration: underline; +} + +/* Table styling */ +.cookie-table { + width: 100%; + margin: 20px auto; + border-collapse: collapse; + text-align: left; + font-size: 1rem; + color: var(--fg); + background-color: var(--html-bg); + border: 1px solid var(--border); +} + +.cookie-table th, +.cookie-table td { + padding: 12px 15px; + border: 1px solid var(--border); +} + +.cookie-table th { + background-color: var(--search-bg); + color: var(--font-fg); + text-align: center; + font-weight: bold; +} + +.cookie-table tr:nth-child(even) { + background-color: var(--snip-background); +} + +/* Center the table within its section */ +.privacy-section .cookie-table { + margin-left: auto; + margin-right: auto; +} diff --git a/static/js/imageviewer.js b/static/js/imageviewer.js index a68f0e2..4bd667f 100644 --- a/static/js/imageviewer.js +++ b/static/js/imageviewer.js @@ -13,7 +13,7 @@ document.addEventListener('DOMContentLoaded', function() { // Set the innerHTML of viewerOverlay viewerOverlay.innerHTML = `
-
+
diff --git a/static/js/minimenu.js b/static/js/minimenu.js index c1c8a39..4044edb 100644 --- a/static/js/minimenu.js +++ b/static/js/minimenu.js @@ -44,4 +44,13 @@ document.addEventListener('DOMContentLoaded', function () { document.getElementById('languageSelect').addEventListener('change', function () { updateSettings('lang', this.value); }); + + // Show/Hide About QGato + document.getElementById('aboutQGatoBtn').addEventListener('click', function() { + document.getElementById('aboutQGatoModal').style.display = 'block'; + }); + + document.getElementById('close-button').addEventListener('click', function() { + document.getElementById('aboutQGatoModal').style.display = 'none'; + }); }); \ No newline at end of file diff --git a/templates/files.html b/templates/files.html index 0d9c7c4..a47bf4e 100755 --- a/templates/files.html +++ b/templates/files.html @@ -43,7 +43,7 @@ {{end}} - +
@@ -53,6 +53,27 @@ + +
+ + + + +
+

diff --git a/templates/forums.html b/templates/forums.html index f5d91f8..7b9d6dd 100755 --- a/templates/forums.html +++ b/templates/forums.html @@ -43,7 +43,7 @@ {{end}} - +
@@ -53,6 +53,27 @@ + +
+ + + + +
+

diff --git a/templates/images.html b/templates/images.html index cfdcdea..1bb91b7 100755 --- a/templates/images.html +++ b/templates/images.html @@ -52,7 +52,7 @@ {{end}} - +
@@ -61,6 +61,27 @@ + + +
+ + + + +

diff --git a/templates/map.html b/templates/map.html index b75f915..054f910 100644 --- a/templates/map.html +++ b/templates/map.html @@ -58,7 +58,7 @@ {{end}} - + @@ -68,6 +68,27 @@ + +
+ + + + +
+

diff --git a/templates/privacy.html b/templates/privacy.html new file mode 100644 index 0000000..ca55401 --- /dev/null +++ b/templates/privacy.html @@ -0,0 +1,133 @@ + + + + + + Privacy Policy + + + + + + + + + + + + + + + +
+ +
+

Settings

+
+ +
+

Current theme: {{.Theme}}

+
+
Dark Theme
+
Light Theme
+
+
+ + + +
+
+
+
+ +
+ + +
+ + + + +
+ + +
+
+

Privacy Policy

+

Your privacy is important to us. This page outlines our practices.

+
+ +
+

Introduction

+

This website is a Free and Open Source Software (FOSS) project licensed under the AGPL-3.0 license. The project is committed to providing a private and secure experience for all users.

+
+ +
+

Data Collection

+

Our servers do not collect any user data, including IP addresses, browsing history, or any other identifiable information. We respect your privacy and ensure that no user information is logged or stored on our servers.

+
+ +
+

Cookies Used

+

Our cookies are not used to track users or sell user data, they are just used to save your settings.

+

These following cookies are used by this site:

+ + + + + + + + + + + {{ range .CookieRows }} + + + + + + + {{ end }} + + +
+ +
+ + + + + + + + diff --git a/templates/search.html b/templates/search.html index d5d8129..44445fe 100755 --- a/templates/search.html +++ b/templates/search.html @@ -58,7 +58,7 @@ {{end}} - +
@@ -66,6 +66,27 @@ + +
+ + + + +
+
diff --git a/templates/text.html b/templates/text.html index 95fde16..1cbccc2 100755 --- a/templates/text.html +++ b/templates/text.html @@ -43,7 +43,7 @@ {{end}} - +
@@ -53,6 +53,27 @@ + +
+ + + + +
+

diff --git a/templates/videos.html b/templates/videos.html index 48bf0f1..15188ac 100644 --- a/templates/videos.html +++ b/templates/videos.html @@ -43,7 +43,7 @@ {{end}} - +
@@ -53,6 +53,27 @@ + +
+ + + + +
+

diff --git a/user-settings.go b/user-settings.go index a18478d..a872f11 100755 --- a/user-settings.go +++ b/user-settings.go @@ -18,44 +18,44 @@ func loadUserSettings(w http.ResponseWriter, r *http.Request) UserSettings { var settings UserSettings saveRequired := false - // Load theme - if cookie, err := r.Cookie("theme"); err == nil { - settings.Theme = cookie.Value - } else { - settings.Theme = "dark" - saveRequired = true - } - - // Determine if the selected theme is dark - settings.IsThemeDark = settings.Theme == "dark" || settings.Theme == "night" || settings.Theme == "black" || settings.Theme == "latte" - - // Load site language - if cookie, err := r.Cookie("site_language"); err == nil { - settings.SiteLanguage = cookie.Value - } else { - // If no site language is set, use Accept-Language or default to "en" - acceptLang := r.Header.Get("Accept-Language") - if acceptLang != "" { - settings.SiteLanguage = normalizeLangCode(strings.Split(acceptLang, ",")[0]) + 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 { - settings.SiteLanguage = "en" // Default language + // 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 + } } - saveRequired = true } - // Load search language (can be empty) - if cookie, err := r.Cookie("search_language"); err == nil { - settings.SearchLanguage = cookie.Value - } - - // Load safe search - if cookie, err := r.Cookie("safe"); err == nil { - settings.SafeSearch = cookie.Value - } else { - settings.SafeSearch = "" - saveRequired = true - } + // 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) } @@ -66,38 +66,16 @@ func loadUserSettings(w http.ResponseWriter, r *http.Request) UserSettings { func saveUserSettings(w http.ResponseWriter, settings UserSettings) { expiration := time.Now().Add(90 * 24 * time.Hour) - http.SetCookie(w, &http.Cookie{ - Name: "theme", - Value: settings.Theme, - Path: "/", - Expires: expiration, - Secure: true, - SameSite: http.SameSiteStrictMode, - }) - http.SetCookie(w, &http.Cookie{ - Name: "site_language", - Value: settings.SiteLanguage, - Path: "/", - Expires: expiration, - Secure: true, - SameSite: http.SameSiteStrictMode, - }) - http.SetCookie(w, &http.Cookie{ - Name: "search_language", - Value: settings.SearchLanguage, - Path: "/", - Expires: expiration, - Secure: true, - SameSite: http.SameSiteStrictMode, - }) - http.SetCookie(w, &http.Cookie{ - Name: "safe", - Value: settings.SafeSearch, - Path: "/", - Expires: expiration, - Secure: true, - SameSite: http.SameSiteStrictMode, - }) + for _, cd := range AllCookies { + http.SetCookie(w, &http.Cookie{ + Name: cd.Name, + Value: cd.GetValue(settings), + Path: "/", + Expires: expiration, + Secure: true, + SameSite: http.SameSiteStrictMode, + }) + } printDebug("settings saved: %v", settings) } @@ -193,3 +171,84 @@ func isValidLangCode(lang string) bool { } 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 +}