map improvements + added forums search
This commit is contained in:
parent
c848c72aea
commit
8da387f8e9
12 changed files with 424 additions and 65 deletions
149
forums.go
Normal file
149
forums.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
2
main.go
2
main.go
|
@ -91,6 +91,8 @@ func handleSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
videoSearchEndpointHandler(w, r)
|
videoSearchEndpointHandler(w, r)
|
||||||
case "map":
|
case "map":
|
||||||
handleMapSearch(w, query, safe)
|
handleMapSearch(w, query, safe)
|
||||||
|
case "forum":
|
||||||
|
handleForumsSearch(w, query, safe, lang, page)
|
||||||
case "text":
|
case "text":
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
|
|
13
map.go
13
map.go
|
@ -14,7 +14,7 @@ type NominatimResponse struct {
|
||||||
Lon string `json:"lon"`
|
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
|
// URL encode the query
|
||||||
query = url.QueryEscape(query)
|
query = url.QueryEscape(query)
|
||||||
|
|
||||||
|
@ -24,29 +24,29 @@ func geocodeQuery(query string) (latitude, longitude string, err error) {
|
||||||
// Make the HTTP GET request
|
// Make the HTTP GET request
|
||||||
resp, err := http.Get(urlString)
|
resp, err := http.Get(urlString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", false, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Read the response
|
// Read the response
|
||||||
var result []NominatimResponse
|
var result []NominatimResponse
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||||
return "", "", err
|
return "", "", false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there are any results
|
// Check if there are any results
|
||||||
if len(result) > 0 {
|
if len(result) > 0 {
|
||||||
latitude = result[0].Lat
|
latitude = result[0].Lat
|
||||||
longitude = result[0].Lon
|
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) {
|
func handleMapSearch(w http.ResponseWriter, query string, lang string) {
|
||||||
// Geocode the query to get coordinates
|
// Geocode the query to get coordinates
|
||||||
latitude, longitude, err := geocodeQuery(query)
|
latitude, longitude, found, err := geocodeQuery(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error geocoding query: %s, error: %v", query, err)
|
log.Printf("Error geocoding query: %s, error: %v", query, err)
|
||||||
http.Error(w, "Failed to find location", http.StatusInternalServerError)
|
http.Error(w, "Failed to find location", http.StatusInternalServerError)
|
||||||
|
@ -58,6 +58,7 @@ func handleMapSearch(w http.ResponseWriter, query string, lang string) {
|
||||||
"Query": query,
|
"Query": query,
|
||||||
"Latitude": latitude,
|
"Latitude": latitude,
|
||||||
"Longitude": longitude,
|
"Longitude": longitude,
|
||||||
|
"Found": found,
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl, err := template.ParseFiles("templates/map.html")
|
tmpl, err := template.ParseFiles("templates/map.html")
|
||||||
|
|
2
run.sh
2
run.sh
|
@ -1,3 +1,3 @@
|
||||||
#!/bin/bash
|
#!/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
|
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
|
|
@ -1349,6 +1349,23 @@ p {
|
||||||
letter-spacing: normal;
|
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 */
|
/* Variables for light theme */
|
||||||
:root {
|
:root {
|
||||||
--background-color: #ffffff;
|
--background-color: #ffffff;
|
||||||
|
|
90
templates/forums.html
Normal file
90
templates/forums.html
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{.Query}} - Ocásek</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form action="/search" id="prev-next-form" class="results-search-container" method="GET" autocomplete="off">
|
||||||
|
<h1 class="logomobile"><a class="no-decoration" href="./">Ocásek</a></h1>
|
||||||
|
<div class="wrapper-results">
|
||||||
|
<input type="text" name="q" value="{{ .Query }}" id="search-input" placeholder="Type to search..." />
|
||||||
|
<button id="search-wrapper-ico" class="material-icons-round" name="t" value="forum">search</button>
|
||||||
|
<input type="submit" class="hide" name="t" value="forum" />
|
||||||
|
</div>
|
||||||
|
<div class="sub-search-button-wrapper">
|
||||||
|
<div class="search-container-results-btn">
|
||||||
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="text">search</button>
|
||||||
|
<button name="t" value="text" class="clickable">Web</button>
|
||||||
|
</div>
|
||||||
|
<div class="search-container-results-btn">
|
||||||
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="image">image</button>
|
||||||
|
<button name="t" value="image" class="clickable">Images</button>
|
||||||
|
</div>
|
||||||
|
<div class="search-container-results-btn">
|
||||||
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="video">movie</button>
|
||||||
|
<button name="t" value="video" class="clickable">Videos</button>
|
||||||
|
</div>
|
||||||
|
<div class="search-container-results-btn">
|
||||||
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable search-active" name="t" value="forum">forum</button>
|
||||||
|
<button name="t" value="forum" class="clickable search-active">Forums</button>
|
||||||
|
</div>
|
||||||
|
<div id="content" class="js-enabled">
|
||||||
|
<div class="search-container-results-btn">
|
||||||
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="map">map</button>
|
||||||
|
<button name="t" value="map" class="clickable">Maps</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="search-container-results-btn">
|
||||||
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="torrent">share</button>
|
||||||
|
<button name="t" value="torrent" class="clickable">Torrents</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<form class="results_settings" action="/search" method="get">
|
||||||
|
<input type="hidden" name="q" value="{{ .Query }}">
|
||||||
|
<select class="results-settings" name="safe" id="safeSearchSelect">
|
||||||
|
<option value="">Safe Search Off</option>
|
||||||
|
<option value="active">Safe Search On</option>
|
||||||
|
</select>
|
||||||
|
<select class="results-settings" name="lang" id="languageSelect">
|
||||||
|
{{range .LanguageOptions}}
|
||||||
|
<option value="{{.Code}}" {{if eq .Code $.CurrentLang}}selected{{end}}>{{.Name}}</option>
|
||||||
|
{{end}}
|
||||||
|
</select>
|
||||||
|
<button class="results-save" name="t" value="text">Apply settings</button>
|
||||||
|
</form>
|
||||||
|
<div class="results">
|
||||||
|
{{if .Results}}
|
||||||
|
{{range .Results}}
|
||||||
|
<div class="result_item">
|
||||||
|
<a href="{{.URL}}">{{.URL}}</a>
|
||||||
|
<a href="{{.URL}}"><h3>{{.Header}}</h3></a>
|
||||||
|
<p>{{.Description}}</p>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
{{end}}
|
||||||
|
{{else}}
|
||||||
|
<div class="no-results">No results found for '{{ .Query }}'. Try different keywords.</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div class="prev-next prev-img">
|
||||||
|
<form action="/search" method="get">
|
||||||
|
<input type="hidden" name="q" value="{{ .Query }}">
|
||||||
|
<input type="hidden" name="t" value="text">
|
||||||
|
{{ if .HasPrevPage }}
|
||||||
|
<button type="submit" name="p" value="{{ sub .Page 1 }}">Previous</button>
|
||||||
|
{{ end }}
|
||||||
|
{{ if .HasNextPage }}
|
||||||
|
<button type="submit" name="p" value="{{ add .Page 1 }}">Next</button>
|
||||||
|
{{ end }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
// Check if JavaScript is enabled and modify the DOM accordingly
|
||||||
|
document.getElementById('content').classList.remove('js-enabled');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -28,13 +28,13 @@
|
||||||
<button name="t" value="video" class="clickable">Videos</button>
|
<button name="t" value="video" class="clickable">Videos</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="search-container-results-btn">
|
<div class="search-container-results-btn">
|
||||||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="reddit">forum</button>
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="forum">forum</button>
|
||||||
<button name="t" value="forum" class="clickable">Forums</button>
|
<button name="t" value="forum" class="clickable">Forums</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="content" class="js-enabled">
|
<div id="content" class="js-enabled">
|
||||||
<div class="search-container-results-btn">
|
<div class="search-container-results-btn">
|
||||||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="map">map</button>
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="map">map</button>
|
||||||
<button name="t" value="map" class="clickable">Map</button>
|
<button name="t" value="map" class="clickable">Maps</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="search-container-results-btn">
|
<div class="search-container-results-btn">
|
||||||
|
|
|
@ -15,18 +15,9 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
#map {
|
#map {
|
||||||
height: 100%;
|
height: calc(100% - 100px);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
top: 100px;
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
@ -38,28 +29,118 @@
|
||||||
<button id="search-wrapper-ico" class="material-icons-round" name="t" value="map">search</button>
|
<button id="search-wrapper-ico" class="material-icons-round" name="t" value="map">search</button>
|
||||||
<input type="submit" class="hide" name="t" value="map" />
|
<input type="submit" class="hide" name="t" value="map" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sub-search-button-wrapper">
|
||||||
|
<div class="search-container-results-btn">
|
||||||
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="text">search</button>
|
||||||
|
<button name="t" value="text" class="clickable">Web</button>
|
||||||
|
</div>
|
||||||
|
<div class="search-container-results-btn">
|
||||||
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="image">image</button>
|
||||||
|
<button name="t" value="image" class="clickable">Images</button>
|
||||||
|
</div>
|
||||||
|
<div class="search-container-results-btn">
|
||||||
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="video">movie</button>
|
||||||
|
<button name="t" value="video" class="clickable">Videos</button>
|
||||||
|
</div>
|
||||||
|
<div class="search-container-results-btn">
|
||||||
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="forum">forum</button>
|
||||||
|
<button name="t" value="forum" class="clickable">Forums</button>
|
||||||
|
</div>
|
||||||
|
<div class="search-container-results-btn">
|
||||||
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable search-active" name="t" value="map">map</button>
|
||||||
|
<button name="t" value="map" class="clickable search-active">Maps</button>
|
||||||
|
</div>
|
||||||
|
<div class="search-container-results-btn">
|
||||||
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="torrent">share</button>
|
||||||
|
<button name="t" value="torrent" class="clickable">Torrents</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div id="map"></div>
|
{{ if .Found }}
|
||||||
<script>
|
<div id="map"></div>
|
||||||
var map = L.map('map').setView([50.0755, 14.4378], 13); // Default to Prague, Czech Republic
|
{{ else }}
|
||||||
|
<div id="map"></div>
|
||||||
|
<div class="message">No results found for "{{ .Query }}". Please try another search.</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Found }}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
var map = L.map('map').setView([{{ .Latitude }}, {{ .Longitude }}], 13); // Set view to found coordinates
|
||||||
|
|
||||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
// Base layers
|
||||||
maxZoom: 19,
|
var streets = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
attribution: '© OpenStreetMap contributors'
|
maxZoom: 19,
|
||||||
}).addTo(map);
|
attribution: '© OpenStreetMap contributors'
|
||||||
|
});
|
||||||
|
|
||||||
// Use the passed coordinates to update the map
|
var satellite = L.tileLayer('https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', {
|
||||||
var latitude = {{ .Latitude }};
|
maxZoom: 19,
|
||||||
var longitude = {{ .Longitude }};
|
attribution: '© OpenStreetMap contributors'
|
||||||
|
});
|
||||||
|
|
||||||
L.marker([latitude, longitude]).addTo(map)
|
var esriSat = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{x}/{y}', {
|
||||||
.bindPopup('{{ .Query }}')
|
maxZoom: 19,
|
||||||
|
attribution: 'Tiles © Esri'
|
||||||
|
});
|
||||||
|
|
||||||
function updateMap(latitude, longitude) {
|
var topo = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
|
||||||
map.setView([latitude, longitude], 13); // Set view to new coordinates
|
maxZoom: 17,
|
||||||
}
|
attribution: '© OpenTopoMap contributors'
|
||||||
|
});
|
||||||
|
|
||||||
updateMap(latitude, longitude); // Update the map view to the new location
|
var baseMaps = {
|
||||||
</script>
|
"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);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{{ end }}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -28,9 +28,15 @@
|
||||||
<button name="t" value="video" class="clickable">Videos</button>
|
<button name="t" value="video" class="clickable">Videos</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="search-container-results-btn">
|
<div class="search-container-results-btn">
|
||||||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="reddit">forum</button>
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="forum">forum</button>
|
||||||
<button name="t" value="forum" class="clickable">Forums</button>
|
<button name="t" value="forum" class="clickable">Forums</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="content" class="js-enabled">
|
||||||
|
<div class="search-container-results-btn">
|
||||||
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="map">map</button>
|
||||||
|
<button name="t" value="map" class="clickable">Maps</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="search-container-results-btn">
|
<div class="search-container-results-btn">
|
||||||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="torrent">share</button>
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="torrent">share</button>
|
||||||
<button name="t" value="torrent" class="clickable">Torrents</button>
|
<button name="t" value="torrent" class="clickable">Torrents</button>
|
||||||
|
@ -66,5 +72,9 @@
|
||||||
<input type="submit" class="results-settings" value="Save">
|
<input type="submit" class="results-settings" value="Save">
|
||||||
</form>
|
</form>
|
||||||
<div>
|
<div>
|
||||||
|
<script>
|
||||||
|
// Check if JavaScript is enabled and modify the DOM accordingly
|
||||||
|
document.getElementById('content').classList.remove('js-enabled');
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -28,13 +28,13 @@
|
||||||
<button name="t" value="video" class="clickable">Videos</button>
|
<button name="t" value="video" class="clickable">Videos</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="search-container-results-btn">
|
<div class="search-container-results-btn">
|
||||||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="reddit">forum</button>
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="forum">forum</button>
|
||||||
<button name="t" value="forum" class="clickable">Forums</button>
|
<button name="t" value="forum" class="clickable">Forums</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="content" class="js-enabled">
|
<div id="content" class="js-enabled">
|
||||||
<div class="search-container-results-btn">
|
<div class="search-container-results-btn">
|
||||||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="map">map</button>
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="map">map</button>
|
||||||
<button name="t" value="map" class="clickable">Map</button>
|
<button name="t" value="map" class="clickable">Maps</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="search-container-results-btn">
|
<div class="search-container-results-btn">
|
||||||
|
|
|
@ -28,13 +28,13 @@
|
||||||
<button name="t" value="video" class="clickable search-active">Videos</button>
|
<button name="t" value="video" class="clickable search-active">Videos</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="search-container-results-btn">
|
<div class="search-container-results-btn">
|
||||||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="reddit">forum</button>
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="forum">forum</button>
|
||||||
<button name="t" value="forum" class="clickable">Forums</button>
|
<button name="t" value="forum" class="clickable">Forums</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="content" class="js-enabled">
|
<div id="content" class="js-enabled">
|
||||||
<div class="search-container-results-btn">
|
<div class="search-container-results-btn">
|
||||||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="map">map</button>
|
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="map">map</button>
|
||||||
<button name="t" value="map" class="clickable">Map</button>
|
<button name="t" value="map" class="clickable">Maps</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="search-container-results-btn">
|
<div class="search-container-results-btn">
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
@ -17,20 +16,7 @@ func PerformGoogleTextSearch(query, safe, lang string, page int) ([]TextSearchRe
|
||||||
var results []TextSearchResult
|
var results []TextSearchResult
|
||||||
|
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
safeParam := "&safe=off"
|
searchURL := buildSearchURL(query, safe, lang, page, resultsPerPage)
|
||||||
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)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", searchURL, nil)
|
req, err := http.NewRequest("GET", searchURL, nil)
|
||||||
if err != 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)
|
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) {
|
doc.Find(".yuRUbf").Each(func(i int, s *goquery.Selection) {
|
||||||
link := s.Find("a")
|
link := s.Find("a")
|
||||||
href, exists := link.Attr("href")
|
href, exists := link.Attr("href")
|
||||||
|
@ -67,8 +82,8 @@ func PerformGoogleTextSearch(query, safe, lang string, page int) ([]TextSearchRe
|
||||||
header := link.Find("h3").Text()
|
header := link.Find("h3").Text()
|
||||||
header = strings.TrimSpace(strings.TrimSuffix(header, "›"))
|
header = strings.TrimSpace(strings.TrimSuffix(header, "›"))
|
||||||
|
|
||||||
descSelection := doc.Find(".VwiC3b").Eq(i)
|
|
||||||
description := ""
|
description := ""
|
||||||
|
descSelection := doc.Find(".VwiC3b").Eq(i)
|
||||||
if descSelection.Length() > 0 {
|
if descSelection.Length() > 0 {
|
||||||
description = descSelection.Text()
|
description = descSelection.Text()
|
||||||
}
|
}
|
||||||
|
@ -84,11 +99,5 @@ func PerformGoogleTextSearch(query, safe, lang string, page int) ([]TextSearchRe
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if len(results) == 0 {
|
return results
|
||||||
if debugMode {
|
|
||||||
log.Println("No results found from Google")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue