package main import ( "crypto/md5" "encoding/hex" "fmt" "net/http" "time" ) var imageSearchEngines []SearchEngine func init() { imageSearchEngines = []SearchEngine{ {Name: "Qwant", Func: wrapImageSearchFunc(PerformQwantImageSearch)}, {Name: "Bing", Func: wrapImageSearchFunc(PerformBingImageSearch)}, {Name: "DeviantArt", Func: wrapImageSearchFunc(PerformDeviantArtImageSearch)}, //{Name: "Imgur", Func: wrapImageSearchFunc(PerformImgurImageSearch), Weight: 4}, // Image proxy not working } } func handleImageSearch(w http.ResponseWriter, settings UserSettings, query string, page int) { startTime := time.Now() cacheKey := CacheKey{Query: query, Page: page, Safe: settings.SafeSearch == "active", Lang: settings.SearchLanguage, Type: "image"} combinedResults := getImageResultsFromCacheOrFetch(cacheKey, query, settings.SafeSearch, settings.SearchLanguage, page) elapsedTime := time.Since(startTime) // Prepare the data to pass to the template data := map[string]interface{}{ "Results": combinedResults, "Query": query, "Fetched": fmt.Sprintf("%.2f %s", elapsedTime.Seconds(), Translate("seconds")), // Time for fetching "Page": page, "HasPrevPage": page > 1, "HasNextPage": len(combinedResults) >= 50, "NoResults": len(combinedResults) == 0, "LanguageOptions": languageOptions, "CurrentLang": settings.SearchLanguage, "Theme": settings.Theme, "Safe": settings.SafeSearch, "IsThemeDark": settings.IsThemeDark, } // Render the template without measuring the time renderTemplate(w, "images.html", data) } func getImageResultsFromCacheOrFetch(cacheKey CacheKey, query, safe, lang string, page int) []ImageSearchResult { cacheChan := make(chan []SearchResult) var combinedResults []ImageSearchResult go func() { results, exists := resultsCache.Get(cacheKey) if exists { printInfo("Cache hit") cacheChan <- results } else { printInfo("Cache miss") cacheChan <- nil } }() select { case results := <-cacheChan: if results == nil { combinedResults = fetchImageResults(query, safe, lang, page) if len(combinedResults) > 0 { resultsCache.Set(cacheKey, convertToSearchResults(combinedResults)) } } else { _, _, imageResults := convertToSpecificResults(results) combinedResults = imageResults } case <-time.After(2 * time.Second): printInfo("Cache check timeout") combinedResults = fetchImageResults(query, safe, lang, page) if len(combinedResults) > 0 { resultsCache.Set(cacheKey, convertToSearchResults(combinedResults)) } } return combinedResults } func fetchImageResults(query, safe, lang string, page int) []ImageSearchResult { var results []ImageSearchResult for _, engine := range imageSearchEngines { printInfo("Using image search engine: %s", engine.Name) searchResults, _, err := engine.Func(query, safe, lang, page) if err != nil { printWarn("Error performing image search with %s: %v", engine.Name, err) continue } for _, result := range searchResults { imageResult := result.(ImageSearchResult) if config.HardCacheDuration > 0 { // Generate hash from the original full-size image URL hasher := md5.New() hasher.Write([]byte(imageResult.Full)) hash := hex.EncodeToString(hasher.Sum(nil)) filename := hash + ".webp" // Set the Full URL to point to the cached image path cacheURL := "/image_cache/" + filename imageResult.ProxyFull = cacheURL // Assign the ID imageResult.ID = hash // Start caching in the background go func(originalURL, filename string) { _, err := cacheImage(originalURL, filename) if err != nil { printWarn("Failed to cache image %s: %v", originalURL, err) } }(imageResult.Full, filename) } results = append(results, imageResult) } if len(results) > 0 { break } } if len(results) == 0 { printWarn("No image results found for query: %s, trying other nodes", query) results = tryOtherNodesForImageSearch(query, safe, lang, page, []string{hostID}) } return results } func wrapImageSearchFunc(f func(string, string, string, int) ([]ImageSearchResult, time.Duration, error)) func(string, string, string, int) ([]SearchResult, time.Duration, error) { return func(query, safe, lang string, page int) ([]SearchResult, time.Duration, error) { imageResults, duration, err := f(query, safe, lang, page) if err != nil { return nil, duration, err } searchResults := make([]SearchResult, len(imageResults)) for i, result := range imageResults { searchResults[i] = result } return searchResults, duration, nil } }