Compare commits
No commits in common. "aa675170dd91e0eff3bd3c54d714f7dbc578e6f4" and "b159980a2658a894cfa90f97ccf6201dabd11bfd" have entirely different histories.
aa675170dd
...
b159980a26
27 changed files with 208 additions and 577 deletions
6
files.go
6
files.go
|
@ -41,8 +41,8 @@ func initializeTorrentSites() {
|
||||||
func handleFileSearch(w http.ResponseWriter, settings UserSettings, query string, page int) {
|
func handleFileSearch(w http.ResponseWriter, settings UserSettings, query string, page int) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
cacheKey := CacheKey{Query: query, Page: page, Safe: settings.SafeSearch == "active", Lang: settings.SearchLanguage, Type: "file"}
|
cacheKey := CacheKey{Query: query, Page: page, Safe: settings.SafeSearch == "active", Lang: settings.Language, Type: "file"}
|
||||||
combinedResults := getFileResultsFromCacheOrFetch(cacheKey, query, settings.SafeSearch, settings.SearchLanguage, page)
|
combinedResults := getFileResultsFromCacheOrFetch(cacheKey, query, settings.SafeSearch, settings.Language, page)
|
||||||
|
|
||||||
sort.Slice(combinedResults, func(i, j int) bool { return combinedResults[i].Seeders > combinedResults[j].Seeders })
|
sort.Slice(combinedResults, func(i, j int) bool { return combinedResults[i].Seeders > combinedResults[j].Seeders })
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ func handleFileSearch(w http.ResponseWriter, settings UserSettings, query string
|
||||||
HasPrevPage: page > 1,
|
HasPrevPage: page > 1,
|
||||||
HasNextPage: len(combinedResults) > 0,
|
HasNextPage: len(combinedResults) > 0,
|
||||||
LanguageOptions: languageOptions,
|
LanguageOptions: languageOptions,
|
||||||
CurrentLang: settings.SearchLanguage,
|
CurrentLang: settings.Language,
|
||||||
Theme: settings.Theme,
|
Theme: settings.Theme,
|
||||||
Safe: settings.SafeSearch,
|
Safe: settings.SafeSearch,
|
||||||
IsThemeDark: settings.IsThemeDark,
|
IsThemeDark: settings.IsThemeDark,
|
||||||
|
|
|
@ -102,7 +102,7 @@ func handleForumsSearch(w http.ResponseWriter, settings UserSettings, query stri
|
||||||
results, err := PerformRedditSearch(query, settings.SafeSearch, page)
|
results, err := PerformRedditSearch(query, settings.SafeSearch, page)
|
||||||
if err != nil || len(results) == 0 { // 0 == 0 to force search by other node
|
if err != nil || len(results) == 0 { // 0 == 0 to force search by other node
|
||||||
log.Printf("No results from primary search, trying other nodes")
|
log.Printf("No results from primary search, trying other nodes")
|
||||||
results = tryOtherNodesForForumSearch(query, settings.SafeSearch, settings.SearchLanguage, page)
|
results = tryOtherNodesForForumSearch(query, settings.SafeSearch, settings.Language, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
data := struct {
|
data := struct {
|
||||||
|
@ -123,7 +123,7 @@ func handleForumsSearch(w http.ResponseWriter, settings UserSettings, query stri
|
||||||
HasPrevPage: page > 1,
|
HasPrevPage: page > 1,
|
||||||
HasNextPage: len(results) == 25,
|
HasNextPage: len(results) == 25,
|
||||||
LanguageOptions: languageOptions,
|
LanguageOptions: languageOptions,
|
||||||
CurrentLang: settings.SearchLanguage,
|
CurrentLang: settings.Language,
|
||||||
Theme: settings.Theme,
|
Theme: settings.Theme,
|
||||||
Safe: settings.SafeSearch,
|
Safe: settings.SafeSearch,
|
||||||
IsThemeDark: settings.IsThemeDark,
|
IsThemeDark: settings.IsThemeDark,
|
||||||
|
|
3
go.mod
3
go.mod
|
@ -23,9 +23,6 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/chai2010/webp v1.1.1 // indirect
|
|
||||||
github.com/disintegration/imaging v1.6.2 // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
golang.org/x/image v0.20.0 // indirect
|
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -2,16 +2,12 @@ github.com/PuerkitoBio/goquery v1.9.1 h1:mTL6XjbJTZdpfL+Gwl5U2h1l9yEkJjhmlTeV9VP
|
||||||
github.com/PuerkitoBio/goquery v1.9.1/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXEpGP2WHRwkbY=
|
github.com/PuerkitoBio/goquery v1.9.1/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXEpGP2WHRwkbY=
|
||||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||||
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
|
|
||||||
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
|
||||||
github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732 h1:XYUCaZrW8ckGWlCRJKCSoh/iFwlpX316a8yY9IFEzv8=
|
github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732 h1:XYUCaZrW8ckGWlCRJKCSoh/iFwlpX316a8yY9IFEzv8=
|
||||||
github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||||
github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93rg=
|
github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93rg=
|
||||||
github.com/chromedp/chromedp v0.9.5/go.mod h1:D4I2qONslauw/C7INoCir1BJkSwBYMyZgx8X276z3+Y=
|
github.com/chromedp/chromedp v0.9.5/go.mod h1:D4I2qONslauw/C7INoCir1BJkSwBYMyZgx8X276z3+Y=
|
||||||
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
||||||
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
|
||||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
|
@ -35,10 +31,6 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
|
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
|
|
||||||
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
@ -13,13 +14,21 @@ func handleImageProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the image from the external URL
|
// Try to fetch the image from Bing first
|
||||||
resp, err := http.Get(imageURL)
|
bingURL := fmt.Sprintf("https://tse.mm.bing.net/th?q=%s", imageURL)
|
||||||
|
resp, err := http.Get(bingURL)
|
||||||
|
if err != nil || resp.StatusCode != http.StatusOK {
|
||||||
|
// If fetching from Bing fails, attempt to fetch from the original image URL
|
||||||
|
printWarn("Error fetching image from Bing, trying original URL.")
|
||||||
|
|
||||||
|
// Attempt to fetch the image directly
|
||||||
|
resp, err = http.Get(imageURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printWarn("Error fetching image: %v", err)
|
printWarn("Error fetching image: %v", err)
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Check if the request was successful
|
// Check if the request was successful
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PerformBingImageSearch performs a Bing image search and returns the results.
|
|
||||||
func PerformBingImageSearch(query, safe, lang string, page int) ([]ImageSearchResult, time.Duration, error) {
|
func PerformBingImageSearch(query, safe, lang string, page int) ([]ImageSearchResult, time.Duration, error) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
|
@ -67,16 +66,14 @@ func PerformBingImageSearch(query, safe, lang string, page int) ([]ImageSearchRe
|
||||||
if err := json.Unmarshal([]byte(metadata), &data); err == nil {
|
if err := json.Unmarshal([]byte(metadata), &data); err == nil {
|
||||||
mediaURL, ok := data["murl"].(string)
|
mediaURL, ok := data["murl"].(string)
|
||||||
if ok {
|
if ok {
|
||||||
// Apply the image proxy
|
|
||||||
proxiedURL := "/imgproxy?url=" + mediaURL
|
|
||||||
results = append(results, ImageSearchResult{
|
results = append(results, ImageSearchResult{
|
||||||
Thumbnail: imgSrc,
|
Thumbnail: imgSrc,
|
||||||
Title: strings.TrimSpace(title),
|
Title: strings.TrimSpace(title),
|
||||||
Media: mediaURL,
|
Media: mediaURL,
|
||||||
Source: mediaURL,
|
|
||||||
ThumbProxy: proxiedURL, // Use the proxied URL
|
|
||||||
Width: width,
|
Width: width,
|
||||||
Height: height,
|
Height: height,
|
||||||
|
Source: mediaURL,
|
||||||
|
ThumbProxy: imgSrc,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,7 +89,6 @@ func PerformBingImageSearch(query, safe, lang string, page int) ([]ImageSearchRe
|
||||||
return results, duration, nil
|
return results, duration, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildBingSearchURL constructs the search URL for Bing Image Search
|
|
||||||
func buildBingSearchURL(query string, page int) string {
|
func buildBingSearchURL(query string, page int) string {
|
||||||
baseURL := "https://www.bing.com/images/search"
|
baseURL := "https://www.bing.com/images/search"
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
|
@ -103,7 +99,6 @@ func buildBingSearchURL(query string, page int) string {
|
||||||
return baseURL + "?" + params.Encode()
|
return baseURL + "?" + params.Encode()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example usage in main (commented out for clarity)
|
|
||||||
// func main() {
|
// func main() {
|
||||||
// results, duration, err := PerformBingImageSearch("kittens", "false", "en", 1)
|
// results, duration, err := PerformBingImageSearch("kittens", "false", "en", 1)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
|
|
|
@ -156,7 +156,7 @@ func PerformDeviantArtImageSearch(query, safe, lang string, page int) ([]ImageSe
|
||||||
Width: 0,
|
Width: 0,
|
||||||
Height: 0,
|
Height: 0,
|
||||||
Source: resultURL,
|
Source: resultURL,
|
||||||
ThumbProxy: "/imgproxy?url=" + imgSrc,
|
ThumbProxy: imgSrc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(imgSrc, resultURL, title)
|
}(imgSrc, resultURL, title)
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -142,36 +141,18 @@ func PerformQwantImageSearch(query, safe, lang string, page int) ([]ImageSearchR
|
||||||
return nil, 0, fmt.Errorf("decoding response: %v", err)
|
return nil, 0, fmt.Errorf("decoding response: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var results []ImageSearchResult
|
||||||
results := make([]ImageSearchResult, len(apiResp.Data.Result.Items))
|
for _, item := range apiResp.Data.Result.Items {
|
||||||
|
results = append(results, ImageSearchResult{
|
||||||
for i, item := range apiResp.Data.Result.Items {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(i int, item struct {
|
|
||||||
Media string `json:"media"`
|
|
||||||
Thumbnail string `json:"thumbnail"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Url string `json:"url"`
|
|
||||||
Width int `json:"width"`
|
|
||||||
Height int `json:"height"`
|
|
||||||
}) {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
// Populate the result
|
|
||||||
results[i] = ImageSearchResult{
|
|
||||||
Thumbnail: item.Thumbnail,
|
Thumbnail: item.Thumbnail,
|
||||||
Title: item.Title,
|
Title: item.Title,
|
||||||
Media: item.Media,
|
Media: item.Media,
|
||||||
Source: item.Url,
|
Source: item.Url,
|
||||||
ThumbProxy: "/imgproxy?url=" + item.Media,
|
ThumbProxy: "/img_proxy?url=" + url.QueryEscape(item.Media), // New proxy not exactly working as intended
|
||||||
Width: item.Width,
|
Width: item.Width,
|
||||||
Height: item.Height,
|
Height: item.Height,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}(i, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for all goroutines to complete
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
duration := time.Since(startTime) // Calculate the duration
|
duration := time.Since(startTime) // Calculate the duration
|
||||||
|
|
||||||
|
|
12
images.go
12
images.go
|
@ -13,17 +13,17 @@ var imageSearchEngines []SearchEngine
|
||||||
func init() {
|
func init() {
|
||||||
imageSearchEngines = []SearchEngine{
|
imageSearchEngines = []SearchEngine{
|
||||||
{Name: "Qwant", Func: wrapImageSearchFunc(PerformQwantImageSearch), Weight: 1},
|
{Name: "Qwant", Func: wrapImageSearchFunc(PerformQwantImageSearch), Weight: 1},
|
||||||
{Name: "Bing", Func: wrapImageSearchFunc(PerformBingImageSearch), Weight: 2},
|
{Name: "DeviantArt", Func: wrapImageSearchFunc(PerformDeviantArtImageSearch), Weight: 2},
|
||||||
{Name: "DeviantArt", Func: wrapImageSearchFunc(PerformDeviantArtImageSearch), Weight: 3},
|
{Name: "Bing", Func: wrapImageSearchFunc(PerformBingImageSearch), Weight: 2}, // Bing sometimes returns with low amount of images, this leads to danamica page loading not working
|
||||||
//{Name: "Imgur", Func: wrapImageSearchFunc(PerformImgurImageSearch), Weight: 4}, // Image proxy not working
|
{Name: "Imgur", Func: wrapImageSearchFunc(PerformImgurImageSearch), Weight: 3},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleImageSearch(w http.ResponseWriter, settings UserSettings, query string, page int) {
|
func handleImageSearch(w http.ResponseWriter, settings UserSettings, query string, page int) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
cacheKey := CacheKey{Query: query, Page: page, Safe: settings.SafeSearch == "active", Lang: settings.SearchLanguage, Type: "image"}
|
cacheKey := CacheKey{Query: query, Page: page, Safe: settings.SafeSearch == "active", Lang: settings.Language, Type: "image"}
|
||||||
combinedResults := getImageResultsFromCacheOrFetch(cacheKey, query, settings.SafeSearch, settings.SearchLanguage, page)
|
combinedResults := getImageResultsFromCacheOrFetch(cacheKey, query, settings.SafeSearch, settings.Language, page)
|
||||||
|
|
||||||
elapsedTime := time.Since(startTime)
|
elapsedTime := time.Since(startTime)
|
||||||
tmpl, err := template.New("images.html").Funcs(funcs).ParseFiles("templates/images.html")
|
tmpl, err := template.New("images.html").Funcs(funcs).ParseFiles("templates/images.html")
|
||||||
|
@ -55,7 +55,7 @@ func handleImageSearch(w http.ResponseWriter, settings UserSettings, query strin
|
||||||
HasNextPage: len(combinedResults) >= 50,
|
HasNextPage: len(combinedResults) >= 50,
|
||||||
NoResults: len(combinedResults) == 0,
|
NoResults: len(combinedResults) == 0,
|
||||||
LanguageOptions: languageOptions,
|
LanguageOptions: languageOptions,
|
||||||
CurrentLang: settings.SearchLanguage,
|
CurrentLang: settings.Language,
|
||||||
Theme: settings.Theme,
|
Theme: settings.Theme,
|
||||||
Safe: settings.SafeSearch,
|
Safe: settings.SafeSearch,
|
||||||
IsThemeDark: settings.IsThemeDark,
|
IsThemeDark: settings.IsThemeDark,
|
||||||
|
|
43
main.go
43
main.go
|
@ -17,7 +17,7 @@ type LanguageOption struct {
|
||||||
var settings UserSettings
|
var settings UserSettings
|
||||||
|
|
||||||
var languageOptions = []LanguageOption{
|
var languageOptions = []LanguageOption{
|
||||||
{Code: "", Name: "Any Language"},
|
{Code: "", Name: "Auto-detect"},
|
||||||
{Code: "en", Name: "English"},
|
{Code: "en", Name: "English"},
|
||||||
{Code: "af", Name: "Afrikaans"},
|
{Code: "af", Name: "Afrikaans"},
|
||||||
{Code: "ar", Name: "العربية (Arabic)"},
|
{Code: "ar", Name: "العربية (Arabic)"},
|
||||||
|
@ -69,37 +69,35 @@ var languageOptions = []LanguageOption{
|
||||||
func handleSearch(w http.ResponseWriter, r *http.Request) {
|
func handleSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
query, safe, lang, searchType, page := parseSearchParams(r)
|
query, safe, lang, searchType, page := parseSearchParams(r)
|
||||||
|
|
||||||
|
// Load user settings
|
||||||
settings = loadUserSettings(w, r)
|
settings = loadUserSettings(w, r)
|
||||||
|
|
||||||
|
// Update theme if provided, or use existing settings
|
||||||
theme := r.URL.Query().Get("theme")
|
theme := r.URL.Query().Get("theme")
|
||||||
if theme != "" {
|
if theme != "" {
|
||||||
settings.Theme = theme
|
settings.Theme = theme
|
||||||
saveUserSettings(w, settings)
|
saveUserSettings(w, settings) // Save if theme is updated
|
||||||
} else if settings.Theme == "" {
|
} else if settings.Theme == "" {
|
||||||
settings.Theme = "dark"
|
settings.Theme = "dark" // Default theme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update safe search if provided, or use existing settings
|
||||||
if safe != "" && safe != settings.SafeSearch {
|
if safe != "" && safe != settings.SafeSearch {
|
||||||
settings.SafeSearch = safe
|
settings.SafeSearch = safe
|
||||||
saveUserSettings(w, settings)
|
saveUserSettings(w, settings) // Save if safe search is updated
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update site language if provided, or use existing settings
|
// Update language if provided, or use existing settings
|
||||||
if lang != "" && lang != settings.SiteLanguage {
|
if lang != "" && lang != settings.Language {
|
||||||
settings.SiteLanguage = lang
|
settings.Language = lang
|
||||||
saveUserSettings(w, settings)
|
saveUserSettings(w, settings) // Save if language is updated
|
||||||
} else if settings.SiteLanguage == "" {
|
} else if settings.Language == "" {
|
||||||
settings.SiteLanguage = normalizeLangCode(r.Header.Get("Accept-Language"))
|
// If no language set, auto-detect from browser or default to "en"
|
||||||
saveUserSettings(w, settings)
|
settings.Language = normalizeLangCode(r.Header.Get("Accept-Language"))
|
||||||
}
|
saveUserSettings(w, settings) // Save if language is auto-detected
|
||||||
|
|
||||||
// Update search language (can be empty)
|
|
||||||
searchLang := r.URL.Query().Get("search_lang")
|
|
||||||
if searchLang != settings.SearchLanguage {
|
|
||||||
settings.SearchLanguage = searchLang
|
|
||||||
saveUserSettings(w, settings)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This will do for now (to handle Dark Reader addon)
|
||||||
switch settings.Theme {
|
switch settings.Theme {
|
||||||
case "dark", "black", "night", "latte":
|
case "dark", "black", "night", "latte":
|
||||||
settings.IsThemeDark = true
|
settings.IsThemeDark = true
|
||||||
|
@ -107,18 +105,18 @@ func handleSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
settings.IsThemeDark = false
|
settings.IsThemeDark = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if there is a search query
|
||||||
if query == "" {
|
if query == "" {
|
||||||
|
// If no query is provided, render the search page template
|
||||||
data := struct {
|
data := struct {
|
||||||
LanguageOptions []LanguageOption
|
LanguageOptions []LanguageOption
|
||||||
CurrentLang string
|
CurrentLang string
|
||||||
CurrentSearchLang string
|
|
||||||
Theme string
|
Theme string
|
||||||
Safe string
|
Safe string
|
||||||
IsThemeDark bool
|
IsThemeDark bool
|
||||||
}{
|
}{
|
||||||
LanguageOptions: languageOptions,
|
LanguageOptions: languageOptions,
|
||||||
CurrentLang: settings.SiteLanguage,
|
CurrentLang: settings.Language,
|
||||||
CurrentSearchLang: settings.SearchLanguage,
|
|
||||||
Theme: settings.Theme,
|
Theme: settings.Theme,
|
||||||
Safe: settings.SafeSearch,
|
Safe: settings.SafeSearch,
|
||||||
IsThemeDark: settings.IsThemeDark,
|
IsThemeDark: settings.IsThemeDark,
|
||||||
|
@ -129,6 +127,7 @@ func handleSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle search based on the type
|
||||||
switch searchType {
|
switch searchType {
|
||||||
case "image":
|
case "image":
|
||||||
handleImageSearch(w, settings, query, page)
|
handleImageSearch(w, settings, query, page)
|
||||||
|
@ -184,7 +183,7 @@ func runServer() {
|
||||||
http.HandleFunc("/", handleSearch)
|
http.HandleFunc("/", handleSearch)
|
||||||
http.HandleFunc("/search", handleSearch)
|
http.HandleFunc("/search", handleSearch)
|
||||||
http.HandleFunc("/suggestions", handleSuggestions)
|
http.HandleFunc("/suggestions", handleSuggestions)
|
||||||
http.HandleFunc("/imgproxy", handleImageProxy)
|
http.HandleFunc("/img_proxy", handleImageProxy)
|
||||||
http.HandleFunc("/node", handleNodeRequest)
|
http.HandleFunc("/node", handleNodeRequest)
|
||||||
http.HandleFunc("/settings", handleSettings)
|
http.HandleFunc("/settings", handleSettings)
|
||||||
http.HandleFunc("/save-settings", handleSaveSettings)
|
http.HandleFunc("/save-settings", handleSaveSettings)
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
.search-page-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-page-content h1 {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#search-input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-type-icons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 30px;
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-button {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-button .material-icons-round {
|
|
||||||
font-size: 48px;
|
|
||||||
color: var(--sub-search-wrapper-ico);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-button p {
|
|
||||||
margin-top: 8px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--sub-search-wrapper-ico);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-button:hover .material-icons-round {
|
|
||||||
transition: all .3s ease;
|
|
||||||
color: var(--blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-button:hover p {
|
|
||||||
transition: all .3s ease;
|
|
||||||
color: var(--blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-button button:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
|
@ -53,48 +53,10 @@
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#searchLanguageSelect,
|
@media (max-width: 600px) {
|
||||||
#safeSearchSelect,
|
.theme-link {
|
||||||
#siteLanguageSelect {
|
width: 100%;
|
||||||
border-radius: 4px;
|
}
|
||||||
padding: 6px;
|
|
||||||
font-size: 15px;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
color: var(--font-fg);
|
|
||||||
width: 160px;
|
|
||||||
background: var(--button);
|
|
||||||
float: right;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
text-align: center;
|
|
||||||
box-sizing: border-box; /* Ensures consistent width with padding */
|
|
||||||
}
|
|
||||||
|
|
||||||
#searchLanguageSelect:hover,
|
|
||||||
#safeSearchSelect:hover,
|
|
||||||
#siteLanguageSelect:hover {
|
|
||||||
border: 1px solid #5f6368;
|
|
||||||
/* background-color: var(--button-hover); */
|
|
||||||
}
|
|
||||||
|
|
||||||
.save.save-settings-page {
|
|
||||||
padding: 6px;
|
|
||||||
width: 160px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure correct aligment */
|
|
||||||
.settings-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-row select,
|
|
||||||
.settings-row button {
|
|
||||||
width: 160px;
|
|
||||||
height: 40px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- */
|
/* --- */
|
||||||
|
|
|
@ -67,10 +67,13 @@
|
||||||
/* Support for all WebKit browsers. */
|
/* Support for all WebKit browsers. */
|
||||||
-webkit-font-feature-settings: 'liga';
|
-webkit-font-feature-settings: 'liga';
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
|
||||||
/* Support for Safari and Chrome. */
|
/* Support for Safari and Chrome. */
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
|
|
||||||
/* Support for Firefox. */
|
/* Support for Firefox. */
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
|
||||||
/* Support for IE. */
|
/* Support for IE. */
|
||||||
font-feature-settings: 'liga';
|
font-feature-settings: 'liga';
|
||||||
}
|
}
|
||||||
|
@ -546,9 +549,8 @@ hr {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background: var(--search-bg-input);
|
background: var(--search-bg-input);
|
||||||
border-radius: 22px;
|
border-radius: 22px;
|
||||||
position: relative;
|
position: absolute;
|
||||||
width: 100%;
|
width: 520px;
|
||||||
max-width: 600px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
@ -652,7 +654,7 @@ hr {
|
||||||
|
|
||||||
.settings-nav {
|
.settings-nav {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: 50px;
|
height: 40px;
|
||||||
background-color: var(--search-bg);
|
background-color: var(--search-bg);
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
@ -672,7 +674,6 @@ hr {
|
||||||
.settings-row {
|
.settings-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
|
@ -681,9 +682,7 @@ hr {
|
||||||
|
|
||||||
.settings-row select,
|
.settings-row select,
|
||||||
.settings-row button {
|
.settings-row button {
|
||||||
width: 160px;
|
margin-left: auto;
|
||||||
height: 40px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.kno_wiki {
|
.kno_wiki {
|
||||||
|
@ -774,8 +773,6 @@ form.torrent-sort {
|
||||||
|
|
||||||
#settingsButton {
|
#settingsButton {
|
||||||
transition: all .3s ease;
|
transition: all .3s ease;
|
||||||
/* width: 283px+6px;
|
|
||||||
height: 31px; */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-icon-link {
|
.settings-icon-link {
|
||||||
|
@ -827,12 +824,6 @@ form.torrent-sort {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-menu select:hover {
|
|
||||||
border: 1px solid #5f6368;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all .3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-content {
|
.settings-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -876,7 +867,7 @@ form.torrent-sort {
|
||||||
|
|
||||||
.theme-settings {
|
.theme-settings {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
width: 100%-4px;
|
width: 90%;
|
||||||
border: 1px solid var(--snip-border);
|
border: 1px solid var(--snip-border);
|
||||||
background: var(--snip-background);
|
background: var(--snip-background);
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
|
@ -890,10 +881,6 @@ form.torrent-sort {
|
||||||
margin-left: 3%;
|
margin-left: 3%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-mini-settings {
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-search-div:hover p {
|
.settings-search-div:hover p {
|
||||||
color: #8ab4f8;
|
color: #8ab4f8;
|
||||||
}
|
}
|
||||||
|
@ -1171,7 +1158,7 @@ p {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
left: 28px;
|
left: 38px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-search-button-wrapper button {
|
.sub-search-button-wrapper button {
|
||||||
|
@ -1553,20 +1540,6 @@ body, h1, p, a, input, button {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
.material-icons-round {
|
|
||||||
font-family: 'Material Icons Round';
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
line-height: 1;
|
|
||||||
letter-spacing: normal;
|
|
||||||
text-transform: none;
|
|
||||||
display: inline-block;
|
|
||||||
white-space: nowrap;
|
|
||||||
word-wrap: normal;
|
|
||||||
direction: ltr;
|
|
||||||
} */
|
|
||||||
|
|
||||||
@media only screen and (max-width: 1220px) {
|
@media only screen and (max-width: 1220px) {
|
||||||
|
|
||||||
.snip {
|
.snip {
|
||||||
|
@ -1891,32 +1864,12 @@ body, h1, p, a, input, button {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-button {
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-button button {
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-button p {
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#clearSearch {
|
#clearSearch {
|
||||||
top: 6px;
|
top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This is really bad */
|
|
||||||
@media only screen and (max-width: 400px) {
|
|
||||||
|
|
||||||
.icon-button {
|
|
||||||
padding: 5%
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensuring dark theme compliance */
|
/* Ensuring dark theme compliance */
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
.leaflet-control-locate,
|
.leaflet-control-locate,
|
||||||
|
@ -1938,7 +1891,6 @@ body, h1, p, a, input, button {
|
||||||
color: var(--link) !important;
|
color: var(--link) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
:root {
|
:root {
|
||||||
--background-color: #ffffff;
|
--background-color: #ffffff;
|
||||||
|
|
|
@ -52,7 +52,6 @@ async function getSuggestions(query) {
|
||||||
|
|
||||||
let currentIndex = -1; // Keep track of the currently selected suggestion
|
let currentIndex = -1; // Keep track of the currently selected suggestion
|
||||||
|
|
||||||
// Handle click events on the type buttons
|
|
||||||
let results = [];
|
let results = [];
|
||||||
searchInput.addEventListener('input', async () => {
|
searchInput.addEventListener('input', async () => {
|
||||||
let input = searchInput.value;
|
let input = searchInput.value;
|
||||||
|
@ -129,6 +128,15 @@ function renderResults(results) {
|
||||||
resultsWrapper.innerHTML = `<ul>${content}</ul>`;
|
resultsWrapper.innerHTML = `<ul>${content}</ul>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to handle search input
|
||||||
|
searchInput.addEventListener('input', async () => {
|
||||||
|
let input = searchInput.value;
|
||||||
|
if (input.length) {
|
||||||
|
const results = await getSuggestions(input);
|
||||||
|
renderResults(results);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Handle click events on the type buttons
|
// Handle click events on the type buttons
|
||||||
const typeButtons = document.querySelectorAll('[name="t"]');
|
const typeButtons = document.querySelectorAll('[name="t"]');
|
||||||
typeButtons.forEach(button => {
|
typeButtons.forEach(button => {
|
||||||
|
@ -170,11 +178,11 @@ document.addEventListener('click', (event) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// // Update visual feedback for selected type on page load
|
// Update visual feedback for selected type on page load
|
||||||
// document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
// const activeButton = document.querySelector(`[name="t"][value="${selectedType}"]`);
|
const activeButton = document.querySelector(`[name="t"][value="${selectedType}"]`);
|
||||||
// if (activeButton) {
|
if (activeButton) {
|
||||||
// typeButtons.forEach(btn => btn.classList.remove('search-active'));
|
typeButtons.forEach(btn => btn.classList.remove('search-active'));
|
||||||
// activeButton.classList.add('search-active');
|
activeButton.classList.add('search-active');
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
|
|
164
suggestions.go
164
suggestions.go
|
@ -6,67 +6,8 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SuggestionSource represents a search suggestion source along with its latency.
|
|
||||||
type SuggestionSource struct {
|
|
||||||
Name string
|
|
||||||
FetchFunc func(string) []string
|
|
||||||
Latency time.Duration
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize suggestion sources with default latency values.
|
|
||||||
var suggestionSources = []SuggestionSource{
|
|
||||||
{
|
|
||||||
Name: "DuckDuckGo",
|
|
||||||
FetchFunc: fetchDuckDuckGoSuggestions,
|
|
||||||
Latency: 50 * time.Millisecond,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Edge",
|
|
||||||
FetchFunc: fetchEdgeSuggestions,
|
|
||||||
Latency: 50 * time.Millisecond,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Brave",
|
|
||||||
FetchFunc: fetchBraveSuggestions,
|
|
||||||
Latency: 50 * time.Millisecond,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Ecosia",
|
|
||||||
FetchFunc: fetchEcosiaSuggestions,
|
|
||||||
Latency: 50 * time.Millisecond,
|
|
||||||
},
|
|
||||||
// { // Not working with fetchSuggestionsFromURL func
|
|
||||||
// Name: "Qwant",
|
|
||||||
// FetchFunc: fetchQwantSuggestions,
|
|
||||||
// Latency: 50 * time.Millisecond,
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
Name: "Startpage",
|
|
||||||
FetchFunc: fetchStartpageSuggestions,
|
|
||||||
Latency: 50 * time.Millisecond,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Yahoo",
|
|
||||||
FetchFunc: fetchYahooSuggestions,
|
|
||||||
Latency: 50 * time.Millisecond,
|
|
||||||
},
|
|
||||||
// I advise against it, but you can use it if you want to
|
|
||||||
// {
|
|
||||||
// Name: "Google",
|
|
||||||
// FetchFunc: fetchGoogleSuggestions,
|
|
||||||
// Latency: 500 * time.Millisecond,
|
|
||||||
// },
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mutex to protect the suggestionSources during sorting.
|
|
||||||
var suggestionsMU sync.Mutex
|
|
||||||
|
|
||||||
func handleSuggestions(w http.ResponseWriter, r *http.Request) {
|
func handleSuggestions(w http.ResponseWriter, r *http.Request) {
|
||||||
query := r.URL.Query().Get("q")
|
query := r.URL.Query().Get("q")
|
||||||
if query == "" {
|
if query == "" {
|
||||||
|
@ -75,58 +16,37 @@ func handleSuggestions(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the suggestion sources based on their latency.
|
// Define the fallback sequence with Google lower in the hierarchy
|
||||||
suggestionsMU.Lock()
|
suggestionSources := []func(string) []string{
|
||||||
sort.Slice(suggestionSources, func(i, j int) bool {
|
fetchDuckDuckGoSuggestions,
|
||||||
return suggestionSources[i].Latency < suggestionSources[j].Latency
|
fetchEdgeSuggestions,
|
||||||
})
|
fetchBraveSuggestions,
|
||||||
suggestionsMU.Unlock()
|
fetchEcosiaSuggestions,
|
||||||
|
fetchQwantSuggestions,
|
||||||
|
fetchStartpageSuggestions,
|
||||||
|
// fetchGoogleSuggestions, // I advise against it, but you can use it if you want to
|
||||||
|
}
|
||||||
|
|
||||||
var suggestions []string
|
var suggestions []string
|
||||||
for i := range suggestionSources {
|
for _, fetchFunc := range suggestionSources {
|
||||||
source := &suggestionSources[i]
|
suggestions = fetchFunc(query)
|
||||||
start := time.Now()
|
|
||||||
suggestions = source.FetchFunc(query)
|
|
||||||
elapsed := time.Since(start)
|
|
||||||
|
|
||||||
updateLatency(source, elapsed)
|
|
||||||
|
|
||||||
if len(suggestions) > 0 {
|
if len(suggestions) > 0 {
|
||||||
printDebug("Suggestions found using %s", source.Name)
|
printDebug("Suggestions found using %T", fetchFunc)
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
printWarn("%s did not return any suggestions or failed.", source.Name)
|
printWarn("%T did not return any suggestions or failed.", fetchFunc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trim the suggestions to a maximum of 8 items
|
|
||||||
suggestions = trimSuggestions(suggestions)
|
|
||||||
|
|
||||||
if len(suggestions) == 0 {
|
if len(suggestions) == 0 {
|
||||||
printErr("All suggestion services failed. Returning empty response.")
|
printErr("All suggestion services failed. Returning empty response.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the final suggestions as JSON.
|
// Return the final suggestions as JSON
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
fmt.Fprintf(w, `["",%s]`, toJSONStringArray(suggestions))
|
fmt.Fprintf(w, `["",%s]`, toJSONStringArray(suggestions))
|
||||||
}
|
}
|
||||||
|
|
||||||
// trimSuggestions trims the suggestion list to a maximum of 8 suggestions.
|
|
||||||
func trimSuggestions(suggestions []string) []string {
|
|
||||||
if len(suggestions) > 8 {
|
|
||||||
return suggestions[:8]
|
|
||||||
}
|
|
||||||
return suggestions
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateLatency updates the latency of a suggestion source using an exponential moving average.
|
|
||||||
func updateLatency(source *SuggestionSource, newLatency time.Duration) {
|
|
||||||
source.mu.Lock()
|
|
||||||
defer source.mu.Unlock()
|
|
||||||
const alpha = 0.5 // Smoothing factor.
|
|
||||||
source.Latency = time.Duration(float64(source.Latency)*(1-alpha) + float64(newLatency)*alpha)
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchGoogleSuggestions(query string) []string {
|
func fetchGoogleSuggestions(query string) []string {
|
||||||
encodedQuery := url.QueryEscape(query)
|
encodedQuery := url.QueryEscape(query)
|
||||||
url := fmt.Sprintf("http://suggestqueries.google.com/complete/search?client=firefox&q=%s", encodedQuery)
|
url := fmt.Sprintf("http://suggestqueries.google.com/complete/search?client=firefox&q=%s", encodedQuery)
|
||||||
|
@ -162,7 +82,6 @@ func fetchEcosiaSuggestions(query string) []string {
|
||||||
return fetchSuggestionsFromURL(url)
|
return fetchSuggestionsFromURL(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is this working?
|
|
||||||
func fetchQwantSuggestions(query string) []string {
|
func fetchQwantSuggestions(query string) []string {
|
||||||
encodedQuery := url.QueryEscape(query)
|
encodedQuery := url.QueryEscape(query)
|
||||||
url := fmt.Sprintf("https://api.qwant.com/v3/suggest?q=%s", encodedQuery)
|
url := fmt.Sprintf("https://api.qwant.com/v3/suggest?q=%s", encodedQuery)
|
||||||
|
@ -177,20 +96,6 @@ func fetchStartpageSuggestions(query string) []string {
|
||||||
return fetchSuggestionsFromURL(url)
|
return fetchSuggestionsFromURL(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchYahooSuggestions(query string) []string {
|
|
||||||
encodedQuery := url.QueryEscape(query)
|
|
||||||
url := fmt.Sprintf("https://search.yahoo.com/sugg/gossip/gossip-us-ura/?output=fxjson&command=%s", encodedQuery)
|
|
||||||
printDebug("Fetching suggestions from Yahoo: %s", url)
|
|
||||||
return fetchSuggestionsFromURL(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
// func fetchBaiduSuggestions(query string) []string {
|
|
||||||
// encodedQuery := url.QueryEscape(query)
|
|
||||||
// url := fmt.Sprintf("https://suggestion.baidu.com/su?wd=%s", encodedQuery)
|
|
||||||
// printDebug("Fetching suggestions from Baidu: %s", url)
|
|
||||||
// return fetchSuggestionsFromURL(url)
|
|
||||||
// }
|
|
||||||
|
|
||||||
func fetchSuggestionsFromURL(url string) []string {
|
func fetchSuggestionsFromURL(url string) []string {
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -205,20 +110,17 @@ func fetchSuggestionsFromURL(url string) []string {
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print the raw HTTP response for debugging
|
// Log the Content-Type for debugging
|
||||||
fmt.Printf("Raw response from %s:\n%s\n", url, string(body))
|
|
||||||
|
|
||||||
// Log the Content-Type for debugging.
|
|
||||||
contentType := resp.Header.Get("Content-Type")
|
contentType := resp.Header.Get("Content-Type")
|
||||||
printDebug("Response Content-Type from %s: %s", url, contentType)
|
printDebug("Response Content-Type from %s: %s", url, contentType)
|
||||||
|
|
||||||
// Check if the body is non-empty.
|
// Check if the body is non-empty
|
||||||
if len(body) == 0 {
|
if len(body) == 0 {
|
||||||
printWarn("Received empty response body from %s", url)
|
printWarn("Received empty response body from %s", url)
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to parse the response as JSON regardless of Content-Type.
|
// Attempt to parse the response as JSON regardless of Content-Type
|
||||||
var parsedResponse []interface{}
|
var parsedResponse []interface{}
|
||||||
if err := json.Unmarshal(body, &parsedResponse); err != nil {
|
if err := json.Unmarshal(body, &parsedResponse); err != nil {
|
||||||
printErr("Error parsing JSON from %s: %v", url, err)
|
printErr("Error parsing JSON from %s: %v", url, err)
|
||||||
|
@ -226,7 +128,7 @@ func fetchSuggestionsFromURL(url string) []string {
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the response structure is as expected.
|
// Ensure the response structure is as expected
|
||||||
if len(parsedResponse) < 2 {
|
if len(parsedResponse) < 2 {
|
||||||
printWarn("Unexpected response format from %v: %v", url, string(body))
|
printWarn("Unexpected response format from %v: %v", url, string(body))
|
||||||
return []string{}
|
return []string{}
|
||||||
|
@ -246,7 +148,6 @@ func fetchSuggestionsFromURL(url string) []string {
|
||||||
return suggestions
|
return suggestions
|
||||||
}
|
}
|
||||||
|
|
||||||
// toJSONStringArray converts a slice of strings to a JSON array string.
|
|
||||||
func toJSONStringArray(strings []string) string {
|
func toJSONStringArray(strings []string) string {
|
||||||
result := ""
|
result := ""
|
||||||
for i, str := range strings {
|
for i, str := range strings {
|
||||||
|
@ -257,30 +158,3 @@ func toJSONStringArray(strings []string) string {
|
||||||
}
|
}
|
||||||
return "[" + result + "]"
|
return "[" + result + "]"
|
||||||
}
|
}
|
||||||
|
|
||||||
// func testSuggestionSources(query string) {
|
|
||||||
// for _, source := range suggestionSources {
|
|
||||||
// fmt.Printf("Testing %s...\n", source.Name)
|
|
||||||
|
|
||||||
// // Fetch suggestions
|
|
||||||
// suggestions := source.FetchFunc(query)
|
|
||||||
|
|
||||||
// // If we get results, print them
|
|
||||||
// if len(suggestions) > 0 {
|
|
||||||
// fmt.Printf("Suggestions from %s:\n", source.Name)
|
|
||||||
// for i, suggestion := range suggestions {
|
|
||||||
// fmt.Printf("%d: %s\n", i+1, suggestion)
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// fmt.Printf("No suggestions from %s.\n", source.Name)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Small separator for clarity
|
|
||||||
// fmt.Println("--------------------------")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func main() {
|
|
||||||
// query := "test query"
|
|
||||||
// testSuggestionSources(query)
|
|
||||||
// }
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<form action="/search" id="prev-next-form" class="results-search-container" method="GET" autocomplete="off">
|
<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>
|
<h1 class="logomobile"><a class="no-decoration" href="./">Ocásek</a></h1>
|
||||||
<div class="wrapper-results">
|
<div class="wrapper-results">
|
||||||
<input type="text" name="q" value="{{ .Query }}" id="search-input"/>
|
<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="file">search</button>
|
<button id="search-wrapper-ico" class="material-icons-round" name="t" value="file">search</button>
|
||||||
<div class="autocomplete">
|
<div class="autocomplete">
|
||||||
<ul>
|
<ul>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<form action="/search" id="prev-next-form" class="results-search-container" method="GET" autocomplete="off">
|
<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>
|
<h1 class="logomobile"><a class="no-decoration" href="./">Ocásek</a></h1>
|
||||||
<div class="wrapper-results">
|
<div class="wrapper-results">
|
||||||
<input type="text" name="q" value="{{ .Query }}" id="search-input"/>
|
<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>
|
<button id="search-wrapper-ico" class="material-icons-round" name="t" value="forum">search</button>
|
||||||
<div class="autocomplete">
|
<div class="autocomplete">
|
||||||
<ul></ul>
|
<ul></ul>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<form action="/search" id="prev-next-form" class="results-search-container" method="GET" autocomplete="off">
|
<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>
|
<h1 class="logomobile"><a class="no-decoration" href="./">Ocásek</a></h1>
|
||||||
<div class="wrapper-results">
|
<div class="wrapper-results">
|
||||||
<input type="text" name="q" value="{{ .Query }}" id="search-input"/>
|
<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="image">search</button>
|
<button id="search-wrapper-ico" class="material-icons-round" name="t" value="image">search</button>
|
||||||
<div class="autocomplete">
|
<div class="autocomplete">
|
||||||
<ul>
|
<ul>
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
<form action="/search" id="prev-next-form" class="results-search-container" method="GET" autocomplete="off">
|
<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>
|
<h1 class="logomobile"><a class="no-decoration" href="./">Ocásek</a></h1>
|
||||||
<div class="wrapper-results">
|
<div class="wrapper-results">
|
||||||
<input type="text" name="q" value="{{ .Query }}" id="search-input"/>
|
<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="map">search</button>
|
<button id="search-wrapper-ico" class="material-icons-round" name="t" value="map">search</button>
|
||||||
<div class="autocomplete">
|
<div class="autocomplete">
|
||||||
<ul>
|
<ul>
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<title>Search with Ocásek</title>
|
<title>Search with Ocásek</title>
|
||||||
<link rel="stylesheet" href="/static/css/style.css">
|
<link rel="stylesheet" href="/static/css/style.css">
|
||||||
<link rel="stylesheet" href="/static/css/style-search.css">
|
|
||||||
<link rel="stylesheet" href="/static/css/{{.Theme}}.css">
|
<link rel="stylesheet" href="/static/css/{{.Theme}}.css">
|
||||||
<link rel="search" type="application/opensearchdescription+xml" title="Ocásek" href="/opensearch.xml">
|
<link rel="search" type="application/opensearchdescription+xml" title="Ocásek" href="/opensearch.xml">
|
||||||
</head>
|
</head>
|
||||||
|
@ -53,17 +52,9 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
// Event listener for Language Selection
|
// Event listener for Language Selection
|
||||||
if (siteLanguageSelect) {
|
document.getElementById('languageSelect').addEventListener('change', function () {
|
||||||
siteLanguageSelect.addEventListener('change', function () {
|
updateSettings('lang', this.value);
|
||||||
updateSettings('site_lang', this.value);
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// if (searchLanguageSelect) {
|
|
||||||
// searchLanguageSelect.addEventListener('change', function () {
|
|
||||||
// updateSettings('search_lang', this.value);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<div class="settings-search-div settings-search-div-search">
|
<div class="settings-search-div settings-search-div-search">
|
||||||
|
@ -73,7 +64,7 @@
|
||||||
<h2>Settings</h2>
|
<h2>Settings</h2>
|
||||||
<div class="settings-content">
|
<div class="settings-content">
|
||||||
<button id="settingsButton" onclick="window.location.href='/settings'">All settings</button> <!-- Well its unessesary to use js here but this menu will not work without js anyway -->
|
<button id="settingsButton" onclick="window.location.href='/settings'">All settings</button> <!-- Well its unessesary to use js here but this menu will not work without js anyway -->
|
||||||
<div class="theme-settings theme-mini-settings">
|
<div class="theme-settings">
|
||||||
<p><span class="highlight">Current theme: </span> <span id="theme_name">{{.Theme}}</span></p>
|
<p><span class="highlight">Current theme: </span> <span id="theme_name">{{.Theme}}</span></p>
|
||||||
<div class="themes-settings-menu">
|
<div class="themes-settings-menu">
|
||||||
<div><img class="view-image-search clickable" id="dark_theme" alt="Dark Theme" src="/static/images/dark.webp"></div>
|
<div><img class="view-image-search clickable" id="dark_theme" alt="Dark Theme" src="/static/images/dark.webp"></div>
|
||||||
|
@ -84,7 +75,7 @@
|
||||||
<option value="disabled" {{if eq .Safe "disabled"}}selected{{end}}>Safe Search Off</option>
|
<option value="disabled" {{if eq .Safe "disabled"}}selected{{end}}>Safe Search Off</option>
|
||||||
<option value="active" {{if eq .Safe "active"}}selected{{end}}>Safe Search On</option>
|
<option value="active" {{if eq .Safe "active"}}selected{{end}}>Safe Search On</option>
|
||||||
</select>
|
</select>
|
||||||
<select class="lang" name="site_lang" id="siteLanguageSelect">
|
<select class="lang" name="lang" id="languageSelect">
|
||||||
{{range .LanguageOptions}}
|
{{range .LanguageOptions}}
|
||||||
<option value="{{.Code}}" {{if eq .Code $.CurrentLang}}selected{{end}}>{{.Name}}</option>
|
<option value="{{.Code}}" {{if eq .Code $.CurrentLang}}selected{{end}}>{{.Name}}</option>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -92,48 +83,20 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form action="/search" class="search-container" method="post" autocomplete="off">
|
<form action="/search" class="search-container" method="post" autocomplete="off">
|
||||||
<div class="search-page-content">
|
|
||||||
<h1>Ocásek</h1>
|
<h1>Ocásek</h1>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<input type="text" name="q" autofocus id="search-input"/> <!-- placeholder="Type to search..." -->
|
<input type="text" name="q" autofocus id="search-input" placeholder="Type to search..." />
|
||||||
<button id="search-wrapper-ico" class="material-icons-round" name="t" value="web" type="submit">search</button>
|
<button id="search-wrapper-ico" class="material-icons-round" name="t" value="text" type="submit">search</button>
|
||||||
<div class="autocomplete">
|
<div class="autocomplete">
|
||||||
<ul></ul>
|
<ul>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- <a id="clearSearch" class="material-icons-round">close</a> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="search-type-icons">
|
<div class="search-button-wrapper">
|
||||||
<input type="hidden" name="p" value="1">
|
<input type="hidden" name="p" value="1">
|
||||||
|
<button name="t" value="text" type="submit">Search Text</button>
|
||||||
<div class="icon-button">
|
<button name="t" value="image" type="submit">Search Images</button>
|
||||||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="text">search</button>
|
|
||||||
<p>Web</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="icon-button">
|
|
||||||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="image">image</button>
|
|
||||||
<p>Images</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="icon-button">
|
|
||||||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="video">movie</button>
|
|
||||||
<p>Videos</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="icon-button">
|
|
||||||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="forum">forum</button>
|
|
||||||
<p>Forums</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="icon-button">
|
|
||||||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="map">map</button>
|
|
||||||
<p>Maps</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="icon-button">
|
|
||||||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="file">share</button>
|
|
||||||
<p>Torrents</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
</a>
|
</a>
|
||||||
<a href="/search?theme=night" class="theme-link">
|
<a href="/search?theme=night" class="theme-link">
|
||||||
<div class="view-image-search clickable" id="night">
|
<div class="view-image-search clickable" id="night">
|
||||||
<img src="/static/images/night.webp" alt="Night">
|
<img src="/static/images/night.webp" alt="night">
|
||||||
<div class="theme-tooltip">Night</div>
|
<div class="theme-tooltip">Night</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -74,25 +74,16 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-row">
|
<div class="settings-row">
|
||||||
<p>Site Language</p>
|
<p>Preferred Language</p>
|
||||||
<select class="results-settings" name="site_lang" id="siteLanguageSelect">
|
<select class="results-settings" name="lang" id="languageSelect">
|
||||||
{{range .LanguageOptions}}
|
{{range .LanguageOptions}}
|
||||||
<option value="{{.Code}}" {{if eq .Code $.CurrentSiteLang}}selected{{end}}>{{.Name}}</option>
|
<option value="{{.Code}}" {{if eq .Code $.CurrentLang}}selected{{end}}>{{.Name}}</option>
|
||||||
{{end}}
|
{{end}}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-row">
|
<div class="settings-row settings-row2">
|
||||||
<p>Search Language</p>
|
<p class="font-hide">|</p>
|
||||||
<select class="results-settings" name="search_lang" id="searchLanguageSelect">
|
|
||||||
{{range .LanguageOptions}}
|
|
||||||
<option value="{{.Code}}" {{if eq .Code $.CurrentSearchLang}}selected{{end}}>{{.Name}}</option>
|
|
||||||
{{end}}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="settings-row">
|
|
||||||
<p class="font-hide"> </p>
|
|
||||||
<button class="save save-settings-page" type="submit">Save Settings</button>
|
<button class="save save-settings-page" type="submit">Save Settings</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<form action="/search" id="prev-next-form" class="results-search-container" method="GET" autocomplete="off">
|
<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>
|
<h1 class="logomobile"><a class="no-decoration" href="./">Ocásek</a></h1>
|
||||||
<div class="wrapper-results">
|
<div class="wrapper-results">
|
||||||
<input type="text" name="q" value="{{ .Query }}" id="search-input"/>
|
<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="text">search</button>
|
<button id="search-wrapper-ico" class="material-icons-round" name="t" value="text">search</button>
|
||||||
<div class="autocomplete">
|
<div class="autocomplete">
|
||||||
<ul>
|
<ul>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<form action="/search" id="prev-next-form" class="results-search-container" method="GET" autocomplete="off">
|
<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>
|
<h1 class="logomobile"><a class="no-decoration" href="./">Ocásek</a></h1>
|
||||||
<div class="wrapper-results">
|
<div class="wrapper-results">
|
||||||
<input type="text" name="q" value="{{ .Query }}" id="search-input"/>
|
<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="video">search</button>
|
<button id="search-wrapper-ico" class="material-icons-round" name="t" value="video">search</button>
|
||||||
<div class="autocomplete">
|
<div class="autocomplete">
|
||||||
<ul>
|
<ul>
|
||||||
|
|
|
@ -66,23 +66,14 @@ func buildSearchURL(query, safe, lang string, page, resultsPerPage int) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
langParam := ""
|
langParam := ""
|
||||||
var glParam, uuleParam string
|
|
||||||
|
|
||||||
if lang != "" {
|
if lang != "" {
|
||||||
// Use lang as the geolocation
|
|
||||||
langParam = "&lr=lang_" + lang
|
langParam = "&lr=lang_" + lang
|
||||||
glParam = "&gl=" + lang
|
|
||||||
uuleParam = ""
|
|
||||||
} else {
|
|
||||||
// Use random geolocation
|
|
||||||
glParam, uuleParam = getRandomGeoLocation()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate random geolocation
|
||||||
|
glParam, uuleParam := getRandomGeoLocation()
|
||||||
|
|
||||||
startIndex := (page - 1) * resultsPerPage
|
startIndex := (page - 1) * resultsPerPage
|
||||||
|
|
||||||
printDebug(fmt.Sprintf("https://www.google.com/search?q=%s%s%s%s%s&start=%d",
|
|
||||||
url.QueryEscape(query), safeParam, langParam, glParam, uuleParam, startIndex))
|
|
||||||
|
|
||||||
return fmt.Sprintf("https://www.google.com/search?q=%s%s%s%s%s&start=%d",
|
return fmt.Sprintf("https://www.google.com/search?q=%s%s%s%s%s&start=%d",
|
||||||
url.QueryEscape(query), safeParam, langParam, glParam, uuleParam, startIndex)
|
url.QueryEscape(query), safeParam, langParam, glParam, uuleParam, startIndex)
|
||||||
}
|
}
|
||||||
|
|
10
text.go
10
text.go
|
@ -22,17 +22,17 @@ func init() {
|
||||||
func HandleTextSearch(w http.ResponseWriter, settings UserSettings, query string, page int) {
|
func HandleTextSearch(w http.ResponseWriter, settings UserSettings, query string, page int) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
cacheKey := CacheKey{Query: query, Page: page, Safe: settings.SafeSearch == "active", Lang: settings.SearchLanguage, Type: "text"}
|
cacheKey := CacheKey{Query: query, Page: page, Safe: settings.SafeSearch == "active", Lang: settings.Language, Type: "text"}
|
||||||
combinedResults := getTextResultsFromCacheOrFetch(cacheKey, query, settings.SafeSearch, settings.SearchLanguage, page)
|
combinedResults := getTextResultsFromCacheOrFetch(cacheKey, query, settings.SafeSearch, settings.Language, page)
|
||||||
|
|
||||||
hasPrevPage := page > 1 // dupe
|
hasPrevPage := page > 1 // dupe
|
||||||
|
|
||||||
//displayResults(w, combinedResults, query, lang, time.Since(startTime).Seconds(), page, hasPrevPage, hasNextPage)
|
//displayResults(w, combinedResults, query, lang, time.Since(startTime).Seconds(), page, hasPrevPage, hasNextPage)
|
||||||
|
|
||||||
// Prefetch next and previous pages
|
// Prefetch next and previous pages
|
||||||
go prefetchPage(query, settings.SafeSearch, settings.SearchLanguage, page+1)
|
go prefetchPage(query, settings.SafeSearch, settings.Language, page+1)
|
||||||
if hasPrevPage {
|
if hasPrevPage {
|
||||||
go prefetchPage(query, settings.SafeSearch, settings.SearchLanguage, page-1)
|
go prefetchPage(query, settings.SafeSearch, settings.Language, page-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
elapsedTime := time.Since(startTime)
|
elapsedTime := time.Since(startTime)
|
||||||
|
@ -65,7 +65,7 @@ func HandleTextSearch(w http.ResponseWriter, settings UserSettings, query string
|
||||||
HasNextPage: len(combinedResults) >= 50,
|
HasNextPage: len(combinedResults) >= 50,
|
||||||
NoResults: len(combinedResults) == 0,
|
NoResults: len(combinedResults) == 0,
|
||||||
LanguageOptions: languageOptions,
|
LanguageOptions: languageOptions,
|
||||||
CurrentLang: settings.SearchLanguage,
|
CurrentLang: settings.Language,
|
||||||
Theme: settings.Theme,
|
Theme: settings.Theme,
|
||||||
Safe: settings.SafeSearch,
|
Safe: settings.SafeSearch,
|
||||||
IsThemeDark: settings.IsThemeDark,
|
IsThemeDark: settings.IsThemeDark,
|
||||||
|
|
|
@ -9,54 +9,51 @@ import (
|
||||||
|
|
||||||
type UserSettings struct {
|
type UserSettings struct {
|
||||||
Theme string
|
Theme string
|
||||||
SiteLanguage string
|
Language string
|
||||||
SearchLanguage string
|
|
||||||
SafeSearch string
|
SafeSearch string
|
||||||
IsThemeDark bool
|
IsThemeDark bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadUserSettings(w http.ResponseWriter, r *http.Request) UserSettings {
|
func loadUserSettings(w http.ResponseWriter, r *http.Request) UserSettings {
|
||||||
var settings UserSettings
|
var settings UserSettings
|
||||||
saveRequired := false
|
saveRequired := false // Track if we need to save settings back
|
||||||
|
|
||||||
// Load theme
|
// Load theme
|
||||||
if cookie, err := r.Cookie("theme"); err == nil {
|
if cookie, err := r.Cookie("theme"); err == nil {
|
||||||
settings.Theme = cookie.Value
|
settings.Theme = cookie.Value
|
||||||
} else {
|
} else {
|
||||||
settings.Theme = "dark"
|
settings.Theme = "dark" // Default theme
|
||||||
saveRequired = true
|
saveRequired = true // No cookie found, need to save this later
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine if the selected theme is dark
|
// Load language
|
||||||
settings.IsThemeDark = settings.Theme == "dark" || settings.Theme == "night" || settings.Theme == "black" || settings.Theme == "latte"
|
if cookie, err := r.Cookie("language"); err == nil {
|
||||||
|
settings.Language = cookie.Value
|
||||||
// Load site language
|
|
||||||
if cookie, err := r.Cookie("site_language"); err == nil {
|
|
||||||
settings.SiteLanguage = cookie.Value
|
|
||||||
} else {
|
} else {
|
||||||
// If no site language is set, use Accept-Language or default to "en"
|
settings.Language = "" // Set language to empty, handled later
|
||||||
|
}
|
||||||
|
|
||||||
|
// If language is empty, get it from the Accept-Language header
|
||||||
|
if settings.Language == "" {
|
||||||
acceptLang := r.Header.Get("Accept-Language")
|
acceptLang := r.Header.Get("Accept-Language")
|
||||||
if acceptLang != "" {
|
if acceptLang != "" {
|
||||||
settings.SiteLanguage = normalizeLangCode(strings.Split(acceptLang, ",")[0])
|
// Get the first language from Accept-Language header and normalize
|
||||||
|
settings.Language = normalizeLangCode(strings.Split(acceptLang, ",")[0])
|
||||||
} else {
|
} else {
|
||||||
settings.SiteLanguage = "en" // Default language
|
settings.Language = "en" // Default language if Accept-Language is not present
|
||||||
}
|
}
|
||||||
saveRequired = true
|
saveRequired = true // No language cookie found, need to save
|
||||||
}
|
|
||||||
|
|
||||||
// Load search language (can be empty)
|
|
||||||
if cookie, err := r.Cookie("search_language"); err == nil {
|
|
||||||
settings.SearchLanguage = cookie.Value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load safe search
|
// Load safe search
|
||||||
if cookie, err := r.Cookie("safe"); err == nil {
|
if cookie, err := r.Cookie("safe"); err == nil {
|
||||||
settings.SafeSearch = cookie.Value
|
settings.SafeSearch = cookie.Value
|
||||||
} else {
|
} else {
|
||||||
settings.SafeSearch = ""
|
settings.SafeSearch = "" // Default safe search off
|
||||||
saveRequired = true
|
saveRequired = true // No safe search cookie found, need to save
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save settings if required (no cookie found for any of the settings)
|
||||||
if saveRequired {
|
if saveRequired {
|
||||||
saveUserSettings(w, settings)
|
saveUserSettings(w, settings)
|
||||||
}
|
}
|
||||||
|
@ -65,27 +62,19 @@ func loadUserSettings(w http.ResponseWriter, r *http.Request) UserSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveUserSettings(w http.ResponseWriter, settings UserSettings) {
|
func saveUserSettings(w http.ResponseWriter, settings UserSettings) {
|
||||||
expiration := time.Now().Add(90 * 24 * time.Hour)
|
expiration := time.Now().Add(90 * 24 * time.Hour) // 90 days from now
|
||||||
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: "theme",
|
Name: "theme",
|
||||||
Value: settings.Theme,
|
Value: settings.Theme,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
Expires: expiration,
|
Expires: expiration, // Expiration time needs to be set otherwise it will expire immediately
|
||||||
Secure: true,
|
Secure: true, // Ensure cookie is sent over HTTPS only
|
||||||
SameSite: http.SameSiteStrictMode,
|
SameSite: http.SameSiteStrictMode,
|
||||||
})
|
})
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: "site_language",
|
Name: "language",
|
||||||
Value: settings.SiteLanguage,
|
Value: settings.Language,
|
||||||
Path: "/",
|
|
||||||
Expires: expiration,
|
|
||||||
Secure: true,
|
|
||||||
SameSite: http.SameSiteStrictMode,
|
|
||||||
})
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
|
||||||
Name: "search_language",
|
|
||||||
Value: settings.SearchLanguage,
|
|
||||||
Path: "/",
|
Path: "/",
|
||||||
Expires: expiration,
|
Expires: expiration,
|
||||||
Secure: true,
|
Secure: true,
|
||||||
|
@ -112,24 +101,15 @@ func handleSaveSettings(w http.ResponseWriter, r *http.Request) {
|
||||||
if theme := r.FormValue("theme"); theme != "" {
|
if theme := r.FormValue("theme"); theme != "" {
|
||||||
settings.Theme = theme
|
settings.Theme = theme
|
||||||
}
|
}
|
||||||
|
if lang := r.FormValue("lang"); lang != "" {
|
||||||
// Update site language if provided
|
settings.Language = lang
|
||||||
if siteLang := r.FormValue("site_lang"); siteLang != "" {
|
|
||||||
settings.SiteLanguage = siteLang
|
|
||||||
} else {
|
} else {
|
||||||
// If site_lang is empty, try to get from Accept-Language header
|
// If lang is empty, try to get from Accept-Language header
|
||||||
acceptLang := r.Header.Get("Accept-Language")
|
acceptLang := r.Header.Get("Accept-Language")
|
||||||
if acceptLang != "" {
|
if acceptLang != "" {
|
||||||
settings.SiteLanguage = strings.Split(acceptLang, ",")[0]
|
settings.Language = 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 != "" {
|
if safe := r.FormValue("safe"); safe != "" {
|
||||||
settings.SafeSearch = safe
|
settings.SafeSearch = safe
|
||||||
}
|
}
|
||||||
|
@ -148,15 +128,13 @@ func handleSettings(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
data := struct {
|
data := struct {
|
||||||
LanguageOptions []LanguageOption
|
LanguageOptions []LanguageOption
|
||||||
CurrentSiteLang string
|
CurrentLang string
|
||||||
CurrentSearchLang string
|
|
||||||
Theme string
|
Theme string
|
||||||
Safe string
|
Safe string
|
||||||
IsThemeDark bool
|
IsThemeDark bool
|
||||||
}{
|
}{
|
||||||
LanguageOptions: languageOptions,
|
LanguageOptions: languageOptions,
|
||||||
CurrentSiteLang: settings.SiteLanguage,
|
CurrentLang: settings.Language,
|
||||||
CurrentSearchLang: settings.SearchLanguage,
|
|
||||||
Theme: settings.Theme,
|
Theme: settings.Theme,
|
||||||
Safe: settings.SafeSearch,
|
Safe: settings.SafeSearch,
|
||||||
IsThemeDark: settings.IsThemeDark,
|
IsThemeDark: settings.IsThemeDark,
|
||||||
|
|
6
video.go
6
video.go
|
@ -151,10 +151,10 @@ func makeHTMLRequest(query, safe, lang string, page int) (*VideoAPIResponse, err
|
||||||
func handleVideoSearch(w http.ResponseWriter, settings UserSettings, query string, page int) {
|
func handleVideoSearch(w http.ResponseWriter, settings UserSettings, query string, page int) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
results := fetchVideoResults(query, settings.SafeSearch, settings.SearchLanguage, page)
|
results := fetchVideoResults(query, settings.SafeSearch, settings.Language, page)
|
||||||
if len(results) == 0 {
|
if len(results) == 0 {
|
||||||
printWarn("No results from primary search, trying other nodes")
|
printWarn("No results from primary search, trying other nodes")
|
||||||
results = tryOtherNodesForVideoSearch(query, settings.SafeSearch, settings.SearchLanguage, page, []string{hostID})
|
results = tryOtherNodesForVideoSearch(query, settings.SafeSearch, settings.Language, page, []string{hostID})
|
||||||
}
|
}
|
||||||
|
|
||||||
elapsed := time.Since(start)
|
elapsed := time.Since(start)
|
||||||
|
@ -173,7 +173,7 @@ func handleVideoSearch(w http.ResponseWriter, settings UserSettings, query strin
|
||||||
"HasPrevPage": page > 1,
|
"HasPrevPage": page > 1,
|
||||||
"HasNextPage": len(results) > 0,
|
"HasNextPage": len(results) > 0,
|
||||||
"LanguageOptions": languageOptions,
|
"LanguageOptions": languageOptions,
|
||||||
"CurrentLang": settings.SearchLanguage,
|
"CurrentLang": settings.Language,
|
||||||
"Theme": settings.Theme,
|
"Theme": settings.Theme,
|
||||||
"Safe": settings.SafeSearch,
|
"Safe": settings.SafeSearch,
|
||||||
"IsThemeDark": settings.IsThemeDark,
|
"IsThemeDark": settings.IsThemeDark,
|
||||||
|
|
Loading…
Reference in a new issue