package main import ( "fmt" "io" "net/http" "net/url" "strings" "time" "github.com/PuerkitoBio/goquery" ) // PerformBraveTextSearch performs a text search on Brave and returns the results. func PerformBraveTextSearch(query, safe, lang string, offset int) ([]TextSearchResult, time.Duration, error) { startTime := time.Now() // Start the timer var results []TextSearchResult // Build the search URL searchURL := fmt.Sprintf("https://search.brave.com/search?q=%s", url.QueryEscape(query)) if offset > 1 { searchURL += fmt.Sprintf("&offset=%d&spellcheck=0", offset-1) } req, err := http.NewRequest("GET", searchURL, nil) if err != nil { printWarn("Error creating request: %v", err) return nil, 0, fmt.Errorf("creating request: %v", err) } TextUserAgent, err := GetUserAgent("Text-Search-Brave") if err != nil { printWarn("Error generating User-Agent: %v", err) return nil, 0, err } req.Header.Set("User-Agent", TextUserAgent) // Single call to DoMetaProxyRequest: resp, err := DoMetaProxyRequest(req) if err != nil { printWarn("Error performing request: %v", err) return nil, 0, fmt.Errorf("performing meta-request: %v", err) } defer resp.Body.Close() // Read the response body body, err := io.ReadAll(resp.Body) if err != nil { printWarn("Error reading response body: %v", err) return nil, 0, fmt.Errorf("reading response body: %v", err) } // Parse the response body doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body))) if err != nil { return nil, 0, fmt.Errorf("parsing HTML: %v", err) } // Only grab .snippet blocks that have data-type="web" doc.Find(`.snippet[data-type="web"]`).Each(func(i int, s *goquery.Selection) { // The main clickable link is <a ... class="heading-serpresult"> anchor := s.Find("a.heading-serpresult").First() link, ok := anchor.Attr("href") if !ok || link == "" { return } // Title is inside <div class="title"> title := strings.TrimSpace(anchor.Find(".title").Text()) if title == "" { // fallback if the .title is slightly off in the DOM title = strings.TrimSpace(s.Find(".title").Text()) } // Description is inside <div class="snippet-description"> desc := strings.TrimSpace(s.Find(".snippet-description").Text()) // Add only if everything is non-empty if title != "" && desc != "" { results = append(results, TextSearchResult{ Header: title, URL: link, Description: desc, }) } }) duration := time.Since(startTime) // Return an error if no results are found if len(results) == 0 { printDebug("No results found for query") return nil, duration, fmt.Errorf("no results found") } printDebug("Search completed successfully found %d results", len(results)) return results, duration, nil }