From 0f42acc77ed84dcf4c3711dec3914cbbdb780117 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 5 Apr 2024 12:44:11 +0200 Subject: [PATCH] updated readme, added image search (website style wip) --- README.md | 39 +++++++++++++- imageresults.go | 71 -------------------------- images.go | 116 ++++++++++++++++++++++++++++++++++++++++++ main.go | 54 ++++++-------------- templates/images.html | 31 +++++++++++ text.go | 49 ++++++++++++++++-- 6 files changed, 245 insertions(+), 115 deletions(-) delete mode 100644 imageresults.go create mode 100644 images.go create mode 100644 templates/images.html diff --git a/README.md b/README.md index 8300a94..ffd68d1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,39 @@ -# Search +# Go Search Engine +This project is a versatile search engine built with Go that leverages external APIs to fetch both image and text search results. It displays these results in a responsive web interface, offering users a comprehensive search experience. + +## Features + +- Image search using the Qwant API. +- Text search using Google search results. +- Responsive web interface for displaying search results with previews. +- Image viewing using proxy and direct links to image source pages for image searches. +- Display of text search results with links to source content. + + +## Getting Started + +### Prerequisites + +- Go (version 1.18 or higher recommended) +- Access to the internet for fetching results from the Qwant API and Google + +### Running the Application + +```bash +git clone https://weforgecode.xyz/Spitfire/Search.git +cd search +go run main.go text.go images.go +``` + +## Project Structure + +- `main.go`: The entry point of the application, setting up the web server and routing. +- `images.go`: Contains logic for handling image search requests, including fetching data from the Qwant API and preparing it for the template. +- `text.go`: Handles text search requests, fetching results from Google and processing them for display. +- `/templates`: Directory containing HTML templates for rendering the search interface and results. + - `search.html`: The main search page template. + - `images.html`: Template for displaying image search results. + - `text.html`: (If applicable) Template for displaying text search results. +- `/static/css`: Directory for CSS stylesheets. + - `style.css`: The main stylesheet for the search interface and results. diff --git a/imageresults.go b/imageresults.go deleted file mode 100644 index 88fcaa6..0000000 --- a/imageresults.go +++ /dev/null @@ -1,71 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "math/rand" - "net/http" - "net/url" - "time" -) - -var userAgents := []string{ - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15", -} - -type ImageResult struct { - ThumbnailURL string `json:"thumb_proxy"` - Source string `json:"source"` -} - -func FetchImageResults(query string) ([]ImageResult, error) { - var results []ImageResult - - // Random user agent - randIndex := rand.Intn(len(userAgents)) - userAgent := userAgents[randIndex] - - client := &http.Client{} - reqURL := fmt.Sprintf("https://api.qwant.com/v3/search/images?t=images&q=%s&count=50&locale=en_CA&offset=1&device=desktop&tgp=2&safesearch=1", url.QueryEscape(query)) - req, err := http.NewRequest("GET", reqURL, nil) - if err != nil { - return nil, err - } - req.Header.Set("User-Agent", userAgent) - - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var respData struct { - Data struct { - Result struct { - Items []struct { - Thumbnail string `json:"thumbnail"` - URL string `json:"url"` - } `json:"items"` - } `json:"result"` - } `json:"data"` - } - - if err := json.NewDecoder(resp.Body).Decode(&respData); err != nil { - return nil, err - } - - for _, item := range respData.Data.Result.Items { - // Process each image result here - results = append(results, ImageResult{ - ThumbnailURL: "/img_proxy?url=" + url.QueryEscape(item.Thumbnail), - Source: url.QueryEscape(urlparse(item.URL).Host), // Ensure you have a urlparse equivalent in Go or process URL differently - }) - } - - return results, nil -} diff --git a/images.go b/images.go new file mode 100644 index 0000000..b027e55 --- /dev/null +++ b/images.go @@ -0,0 +1,116 @@ +package main + +import ( + "encoding/json" + "fmt" + "html/template" + "log" + "net/http" + "net/url" + "time" +) + +// ImageSearchResult represents a single image result +type ImageSearchResult struct { + Thumbnail string + Title string + Media string + Width int + Height int + Source string + ThumbProxy string +} + +// QwantAPIResponse represents the JSON response structure from Qwant API +type QwantAPIResponse struct { + Data struct { + Result struct { + Items []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"` + } `json:"items"` + } `json:"result"` + } `json:"data"` +} + +// FetchImageResults contacts the image search API and returns a slice of ImageSearchResult +func fetchImageResults(query string) ([]ImageSearchResult, error) { + client := &http.Client{Timeout: 10 * time.Second} + + // Update this URL to the actual API endpoint you intend to use + apiURL := fmt.Sprintf("https://api.qwant.com/v3/search/images?t=images&q=%s&count=50&locale=en_CA&offset=1&device=desktop&tgp=2&safesearch=1", url.QueryEscape(query)) + + req, err := http.NewRequest("GET", apiURL, nil) + if err != nil { + return nil, fmt.Errorf("creating request: %v", err) + } + + req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; YourBot/1.0; +http://yourbot.com/bot.html)") + + resp, err := client.Do(req) + 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 apiResp QwantAPIResponse + if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil { + return nil, fmt.Errorf("decoding response: %v", err) + } + + var results []ImageSearchResult + for _, item := range apiResp.Data.Result.Items { + results = append(results, ImageSearchResult{ + Thumbnail: item.Thumbnail, // Thumbnail URL + Title: item.Title, // Image title + Media: item.Media, // Direct link to the image - Ensure this field is used appropriately in your template + Source: item.Media, // Using item.Media here ensures the direct image link is used + ThumbProxy: "/img_proxy?url=" + url.QueryEscape(item.Thumbnail), // Proxy URL for the thumbnail, if needed + Width: item.Width, + Height: item.Height, + }) + } + + return results, nil +} + +// HandleImageSearch is the HTTP handler for image search requests +func handleImageSearch(w http.ResponseWriter, r *http.Request, query string) { + results, err := fetchImageResults(query) + if err != nil { + log.Printf("Error performing image search: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + tmpl, err := template.ParseFiles("templates/images.html") // Ensure path is correct + if err != nil { + log.Printf("Error parsing template: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + data := struct { + Results []ImageSearchResult + Query string + Fetched string + }{ + Results: results, + Query: query, + Fetched: fmt.Sprintf("%.2f seconds", time.Since(time.Now()).Seconds()), + } + + err = tmpl.Execute(w, data) + if err != nil { + log.Printf("Error executing template: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } +} diff --git a/main.go b/main.go index 2968749..449e86a 100644 --- a/main.go +++ b/main.go @@ -2,12 +2,8 @@ package main import ( "fmt" - "html/template" "log" "net/http" - "strconv" - "strings" - "time" ) // LanguageOption represents a language option for search @@ -66,12 +62,12 @@ var languageOptions = []LanguageOption{ {Code: "lang_vi", Name: "Tiếng Việt (Vietnamese)"}, } -var funcs = template.FuncMap{ - "title": func(s string) string { return strings.Title(s) }, - "url_for": func(filename string) string { return "/" + filename }, -} +// var funcs = template.FuncMap{ +// "title": func(s string) string { return strings.Title(s) }, +// "url_for": func(filename string) string { return "/" + filename }, +// } -var templates = template.Must(template.New("").Funcs(funcs).ParseFiles("templates/results.html")) +// var templates = template.Must(template.New("").Funcs(funcs).ParseFiles("templates/results.html")) func main() { http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) @@ -82,16 +78,18 @@ func main() { } func handleSearch(w http.ResponseWriter, r *http.Request) { - var query, safe, lang string + var query, safe, lang, searchType string if r.Method == "GET" { query = r.URL.Query().Get("q") safe = r.URL.Query().Get("safe") lang = r.URL.Query().Get("lang") + searchType = r.URL.Query().Get("t") } else if r.Method == "POST" { query = r.FormValue("q") safe = r.FormValue("safe") lang = r.FormValue("lang") + searchType = r.FormValue("t") } if query == "" { @@ -99,34 +97,12 @@ func handleSearch(w http.ResponseWriter, r *http.Request) { return } - start := time.Now() // This line is correctly placed - results, err := PerformTextSearch(query, safe, lang) - if err != nil { - http.Error(w, "Failed to fetch search results", http.StatusInternalServerError) - return - } - elapsed := time.Since(start) // Correctly captures the elapsed time after search - fetched := fmt.Sprintf("Fetched the results in %.2f seconds", elapsed.Seconds()) - - data := struct { - Results []SearchResult - Query string - Fetched string - ElapsedTime string - LanguageOptions []LanguageOption - CurrentLang string - }{ - Results: results, - Query: query, - Fetched: fetched, - ElapsedTime: strconv.FormatFloat(time.Since(start).Seconds(), 'f', 2, 64), - LanguageOptions: languageOptions, - CurrentLang: lang, - } - - err = templates.ExecuteTemplate(w, "results.html", data) - if err != nil { - http.Error(w, "Failed to render template", http.StatusInternalServerError) - return + switch searchType { + case "text": + handleTextSearch(w, r, query, safe, lang) // Handles fetching and rendering text search results + case "image": + handleImageSearch(w, r, query) // Handles fetching and rendering image search results + default: + http.ServeFile(w, r, "static/search.html") } } diff --git a/templates/images.html b/templates/images.html new file mode 100644 index 0000000..0972b38 --- /dev/null +++ b/templates/images.html @@ -0,0 +1,31 @@ + + + + + + Image Search Results + + + +
+

Image Search Results

+ {{ if .Results }} +
+ {{ range .Results }} +
+ + {{ .Title }} + +
+
{{ .Title }}
+
Source: Visit
+
+
+ {{ end }} +
+ {{ else }} +
No results found for '{{ .Query }}'. Try different keywords.
+ {{ end }} +
+ + diff --git a/text.go b/text.go index 7e88b57..69d946e 100644 --- a/text.go +++ b/text.go @@ -1,22 +1,25 @@ package main import ( + "fmt" + "html/template" "log" "net/http" "net/url" "strings" + "time" "github.com/PuerkitoBio/goquery" ) -type SearchResult struct { +type TextSearchResult struct { URL string Header string Description string } -func PerformTextSearch(query, safe, lang string) ([]SearchResult, error) { - var results []SearchResult +func PerformTextSearch(query, safe, lang string) ([]TextSearchResult, error) { + var results []TextSearchResult client := &http.Client{} safeParam := "&safe=off" @@ -61,7 +64,7 @@ func PerformTextSearch(query, safe, lang string) ([]SearchResult, error) { description = descSelection.Text() } - results = append(results, SearchResult{ + results = append(results, TextSearchResult{ URL: href, Header: header, Description: description, @@ -70,3 +73,41 @@ func PerformTextSearch(query, safe, lang string) ([]SearchResult, error) { return results, nil } + +func handleTextSearch(w http.ResponseWriter, r *http.Request, query, safe, lang string) { + // Perform the text search + results, err := PerformTextSearch(query, safe, lang) + if err != nil { + log.Printf("Error performing text search: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + // Assuming you have a separate template for text search results + tmpl, err := template.ParseFiles("templates/results.html") // Ensure this path matches your templates' location + if err != nil { + log.Printf("Error parsing template: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + data := struct { + Results []TextSearchResult // Ensure this type matches the structure expected by your template + Query string + Fetched string + LanguageOptions []LanguageOption + CurrentLang string + }{ + Results: results, + Query: query, + Fetched: fmt.Sprintf("%.2f seconds", time.Since(time.Now()).Seconds()), // Example fetched time, adjust as necessary + LanguageOptions: languageOptions, // Assuming this is defined globally or elsewhere + CurrentLang: lang, + } + + err = tmpl.Execute(w, data) + if err != nil { + log.Printf("Error executing template: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } +}