From fb8508777c900c829ec54ae3d091b32d6032854c Mon Sep 17 00:00:00 2001 From: partisan Date: Tue, 21 May 2024 21:22:36 +0200 Subject: [PATCH] map improvements + added forums search --- forums.go | 149 ++++++++++++++++++++++++++++++++++++++++ main.go | 2 + map.go | 13 ++-- run.sh | 2 +- static/css/style.css | 17 +++++ templates/forums.html | 90 ++++++++++++++++++++++++ templates/images.html | 4 +- templates/map.html | 137 ++++++++++++++++++++++++++++-------- templates/settings.html | 12 +++- templates/text.html | 4 +- templates/videos.html | 4 +- text-google.go | 55 ++++++++------- 12 files changed, 424 insertions(+), 65 deletions(-) create mode 100644 forums.go create mode 100644 templates/forums.html diff --git a/forums.go b/forums.go new file mode 100644 index 0000000..348bc6c --- /dev/null +++ b/forums.go @@ -0,0 +1,149 @@ +// forums.go +package main + +import ( + "encoding/json" + "fmt" + "html/template" + "math" + "net/http" + "net/url" + "time" +) + +type ForumSearchResult struct { + URL string `json:"url"` + Header string `json:"header"` + Description string `json:"description"` + PublishedDate time.Time `json:"publishedDate"` + ImgSrc string `json:"imgSrc,omitempty"` + ThumbnailSrc string `json:"thumbnailSrc,omitempty"` +} + +func PerformRedditSearch(query string, safe string, page int) ([]ForumSearchResult, error) { + const ( + pageSize = 25 + baseURL = "https://www.reddit.com/" + maxRetries = 5 + initialBackoff = 2 * time.Second + ) + var results []ForumSearchResult + + searchURL := fmt.Sprintf("%ssearch.json?q=%s&limit=%d&start=%d", baseURL, url.QueryEscape(query), pageSize, page*pageSize) + var resp *http.Response + var err error + + // Retry logic with exponential backoff + for i := 0; i <= maxRetries; i++ { + resp, err = http.Get(searchURL) + if err != nil { + return nil, fmt.Errorf("making request: %v", err) + } + if resp.StatusCode != http.StatusTooManyRequests { + break + } + + // Wait for some time before retrying + backoff := time.Duration(math.Pow(2, float64(i))) * initialBackoff + time.Sleep(backoff) + } + + if err != nil { + return nil, fmt.Errorf("making request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + var searchResults map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&searchResults); err != nil { + return nil, fmt.Errorf("decoding response: %v", err) + } + + data, ok := searchResults["data"].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("no data field in response") + } + + posts, ok := data["children"].([]interface{}) + if !ok { + return nil, fmt.Errorf("no children field in data") + } + + for _, post := range posts { + postData := post.(map[string]interface{})["data"].(map[string]interface{}) + + if safe == "active" && postData["over_18"].(bool) { + continue + } + + header := postData["title"].(string) + description := postData["selftext"].(string) + if len(description) > 500 { + description = description[:500] + "..." + } + publishedDate := time.Unix(int64(postData["created_utc"].(float64)), 0) + permalink := postData["permalink"].(string) + resultURL := baseURL + permalink + + result := ForumSearchResult{ + URL: resultURL, + Header: header, + Description: description, + PublishedDate: publishedDate, + } + + thumbnail := postData["thumbnail"].(string) + if parsedURL, err := url.Parse(thumbnail); err == nil && parsedURL.Scheme != "" { + result.ImgSrc = postData["url"].(string) + result.ThumbnailSrc = thumbnail + } + + results = append(results, result) + } + + return results, nil +} + +func handleForumsSearch(w http.ResponseWriter, query, safe, lang string, page int) { + results, err := PerformRedditSearch(query, safe, page) + if err != nil { + http.Error(w, fmt.Sprintf("Error performing search: %v", err), http.StatusInternalServerError) + return + } + + data := struct { + Query string + Results []ForumSearchResult + LanguageOptions []LanguageOption + CurrentLang string + Page int + HasPrevPage bool + HasNextPage bool + }{ + Query: query, + Results: results, + LanguageOptions: languageOptions, + CurrentLang: lang, + Page: page, + HasPrevPage: page > 1, + HasNextPage: len(results) == 25, + } + + funcMap := template.FuncMap{ + "sub": func(a, b int) int { return a - b }, + "add": func(a, b int) int { return a + b }, + } + + tmpl, err := template.New("forums.html").Funcs(funcMap).ParseFiles("templates/forums.html") + if err != nil { + http.Error(w, fmt.Sprintf("Error loading template: %v", err), http.StatusInternalServerError) + return + } + + if err := tmpl.Execute(w, data); err != nil { + http.Error(w, fmt.Sprintf("Error rendering template: %v", err), http.StatusInternalServerError) + } +} diff --git a/main.go b/main.go index 585e98f..9a5ec3a 100644 --- a/main.go +++ b/main.go @@ -91,6 +91,8 @@ func handleSearch(w http.ResponseWriter, r *http.Request) { videoSearchEndpointHandler(w, r) case "map": handleMapSearch(w, query, safe) + case "forum": + handleForumsSearch(w, query, safe, lang, page) case "text": fallthrough default: diff --git a/map.go b/map.go index c260fa7..7ad21ff 100644 --- a/map.go +++ b/map.go @@ -14,7 +14,7 @@ type NominatimResponse struct { Lon string `json:"lon"` } -func geocodeQuery(query string) (latitude, longitude string, err error) { +func geocodeQuery(query string) (latitude, longitude string, found bool, err error) { // URL encode the query query = url.QueryEscape(query) @@ -24,29 +24,29 @@ func geocodeQuery(query string) (latitude, longitude string, err error) { // Make the HTTP GET request resp, err := http.Get(urlString) if err != nil { - return "", "", err + return "", "", false, err } defer resp.Body.Close() // Read the response var result []NominatimResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - return "", "", err + return "", "", false, err } // Check if there are any results if len(result) > 0 { latitude = result[0].Lat longitude = result[0].Lon - return latitude, longitude, nil + return latitude, longitude, true, nil } - return "", "", fmt.Errorf("no results found") + return "", "", false, nil } func handleMapSearch(w http.ResponseWriter, query string, lang string) { // Geocode the query to get coordinates - latitude, longitude, err := geocodeQuery(query) + latitude, longitude, found, err := geocodeQuery(query) if err != nil { log.Printf("Error geocoding query: %s, error: %v", query, err) http.Error(w, "Failed to find location", http.StatusInternalServerError) @@ -58,6 +58,7 @@ func handleMapSearch(w http.ResponseWriter, query string, lang string) { "Query": query, "Latitude": latitude, "Longitude": longitude, + "Found": found, } tmpl, err := template.ParseFiles("templates/map.html") diff --git a/run.sh b/run.sh index 5758a55..f56f9fc 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/bash -go run main.go text-google.go images.go imageproxy.go video.go map.go text.go text-quant.go text-duckduckgo.go cache.go --debug \ No newline at end of file +go run main.go text-google.go images.go imageproxy.go video.go map.go text.go text-quant.go text-duckduckgo.go cache.go forums.go --debug \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css index 2b84caf..c444071 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1349,6 +1349,23 @@ p { letter-spacing: normal; } +.message { + position: absolute; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + padding: 10px; + background-color: var(--html-bg); + border: 1px solid var(--border); + border-radius: 15px; + box-shadow: 0 0 10px rgba(0,0,0,0.1); + z-index: 1000; + width: auto; + max-width: 80%; + text-align: center; + color: var(--text-color); +} + /* Variables for light theme */ :root { --background-color: #ffffff; diff --git a/templates/forums.html b/templates/forums.html new file mode 100644 index 0000000..ce52762 --- /dev/null +++ b/templates/forums.html @@ -0,0 +1,90 @@ + + + + + + {{.Query}} - Ocásek + + + +
+

Ocásek

+
+ + + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+
+ + + + +
+
+ {{if .Results}} + {{range .Results}} +
+ {{.URL}} +

{{.Header}}

+

{{.Description}}

+
+
+ {{end}} + {{else}} +
No results found for '{{ .Query }}'. Try different keywords.
+ {{end}} +
+
+
+ + + {{ if .HasPrevPage }} + + {{ end }} + {{ if .HasNextPage }} + + {{ end }} +
+
+ + + diff --git a/templates/images.html b/templates/images.html index a26b81d..98f07f2 100644 --- a/templates/images.html +++ b/templates/images.html @@ -28,13 +28,13 @@
- +
- +
diff --git a/templates/map.html b/templates/map.html index fab6fb5..44dff6b 100644 --- a/templates/map.html +++ b/templates/map.html @@ -15,18 +15,9 @@ overflow: hidden; } #map { - height: 100%; + height: calc(100% - 100px); width: 100%; - } - .no-decoration { - padding: 1px; - border-radius: 5px; - left: 20px; - } - /* Reposition the Leaflet control container */ - .leaflet-top.leaflet-left { - top: 70px; /* Adjust this value based on your logo's height */ - left: 10px; + top: 100px; } @@ -38,28 +29,118 @@
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
-
- + var baseMaps = { + "Streets": streets, + "Satellite": satellite, + "Esri Satellite": esriSat, + "Topographic": topo + }; + + streets.addTo(map); // Add default layer + + // Layer control + L.control.layers(baseMaps).addTo(map); + + // Marker with passed coordinates + L.marker([{{ .Latitude }}, {{ .Longitude }}]).addTo(map) + .bindPopup('{{ .Query }}'); + + // Add scale control + L.control.scale().addTo(map); + + // Add custom control for geolocation + L.Control.geolocate = L.Control.extend({ + onAdd: function(map) { + var div = L.DomUtil.create('div', 'leaflet-control-locate'); + div.title = 'Locate Me'; + L.DomEvent.on(div, 'click', function() { + map.locate({setView: true, maxZoom: 16}); + }); + return div; + } + }); + + L.control.geolocate = function(opts) { + return new L.Control.geolocate(opts); + } + + L.control.geolocate({ position: 'topright' }).addTo(map); + + // Geolocation function + function onLocationFound(e) { + var radius = e.accuracy / 2; + L.marker(e.latlng).addTo(map) + .bindPopup("You are within " + radius + " meters from this point").openPopup(); + L.circle(e.latlng, radius).addTo(map); + } + + function onLocationError(e) { + alert(e.message); + } + + map.on('locationfound', onLocationFound); + map.on('locationerror', onLocationError); + }); + + {{ end }} diff --git a/templates/settings.html b/templates/settings.html index 80abb19..485073d 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -28,9 +28,15 @@
- +
+
+
+ + +
+
@@ -66,5 +72,9 @@
+ diff --git a/templates/text.html b/templates/text.html index 5f1a093..f056dc2 100644 --- a/templates/text.html +++ b/templates/text.html @@ -28,13 +28,13 @@
- +
- +
diff --git a/templates/videos.html b/templates/videos.html index 7c592aa..352f38c 100644 --- a/templates/videos.html +++ b/templates/videos.html @@ -28,13 +28,13 @@
- +
- +
diff --git a/text-google.go b/text-google.go index 87594c2..2a9b53d 100644 --- a/text-google.go +++ b/text-google.go @@ -6,7 +6,6 @@ import ( "log" "net/http" "net/url" - "strconv" "strings" "github.com/PuerkitoBio/goquery" @@ -17,20 +16,7 @@ func PerformGoogleTextSearch(query, safe, lang string, page int) ([]TextSearchRe var results []TextSearchResult client := &http.Client{} - safeParam := "&safe=off" - if safe == "active" { - safeParam = "&safe=active" - } - - langParam := "" - if lang != "" { - langParam = "&lr=" + lang - } - - // Calculate the start index based on the page number - startIndex := (page - 1) * resultsPerPage - - searchURL := "https://www.google.com/search?q=" + url.QueryEscape(query) + safeParam + langParam + "&udm=14&start=" + strconv.Itoa(startIndex) + searchURL := buildSearchURL(query, safe, lang, page, resultsPerPage) req, err := http.NewRequest("GET", searchURL, nil) if err != nil { @@ -54,6 +40,35 @@ func PerformGoogleTextSearch(query, safe, lang string, page int) ([]TextSearchRe return nil, fmt.Errorf("loading HTML document: %v", err) } + results = parseResults(doc) + + if len(results) == 0 { + if debugMode { + log.Println("No results found from Google") + } + } + + return results, nil +} + +func buildSearchURL(query, safe, lang string, page, resultsPerPage int) string { + safeParam := "&safe=off" + if safe == "active" { + safeParam = "&safe=active" + } + + langParam := "" + if lang != "" { + langParam = "&lr=" + lang + } + + startIndex := (page - 1) * resultsPerPage + return fmt.Sprintf("https://www.google.com/search?q=%s%s%s&udm=14&start=%d", url.QueryEscape(query), safeParam, langParam, startIndex) +} + +func parseResults(doc *goquery.Document) []TextSearchResult { + var results []TextSearchResult + doc.Find(".yuRUbf").Each(func(i int, s *goquery.Selection) { link := s.Find("a") href, exists := link.Attr("href") @@ -67,8 +82,8 @@ func PerformGoogleTextSearch(query, safe, lang string, page int) ([]TextSearchRe header := link.Find("h3").Text() header = strings.TrimSpace(strings.TrimSuffix(header, "›")) - descSelection := doc.Find(".VwiC3b").Eq(i) description := "" + descSelection := doc.Find(".VwiC3b").Eq(i) if descSelection.Length() > 0 { description = descSelection.Text() } @@ -84,11 +99,5 @@ func PerformGoogleTextSearch(query, safe, lang string, page int) ([]TextSearchRe } }) - if len(results) == 0 { - if debugMode { - log.Println("No results found from Google") - } - } - - return results, nil + return results }