package websearch import ( "context" "encoding/json" "fmt" "io" "net/http" "net/url" "strings" "time" ) var braveSearchURL = "https://api.search.brave.com/res/v1/web/search" type Result struct { Title string `json:"title"` URL string `json:"url"` Description string `json:"description"` } type Client struct { http *http.Client } func NewClient() *Client { return &Client{http: &http.Client{Timeout: 15 * time.Second}} } type braveSearchResponse struct { Web *struct { Results []struct { Title string `json:"title"` URL string `json:"url"` Description string `json:"description"` } `json:"results"` } `json:"web"` } func (c *Client) Search(ctx context.Context, provider Provider, query string, count int) ([]Result, error) { query = strings.TrimSpace(query) if query == "" { return nil, fmt.Errorf("search query is required") } if count <= 0 { count = 5 } if count > 20 { count = 20 } switch provider.Type { case ProviderBrave: return c.searchBrave(ctx, provider.APIKey, query, count) default: return nil, fmt.Errorf("unsupported search provider type: %s", provider.Type) } } func (c *Client) searchBrave(ctx context.Context, apiKey, query string, count int) ([]Result, error) { apiKey = strings.TrimSpace(apiKey) if apiKey == "" { return nil, fmt.Errorf("brave api key is required") } u, err := url.Parse(braveSearchURL) if err != nil { return nil, err } q := u.Query() q.Set("q", query) q.Set("count", fmt.Sprintf("%d", count)) u.RawQuery = q.Encode() req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) if err != nil { return nil, err } req.Header.Set("Accept", "application/json") req.Header.Set("Accept-Encoding", "gzip") req.Header.Set("X-Subscription-Token", apiKey) resp, err := c.http.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) if err != nil { return nil, err } if resp.StatusCode >= 400 { return nil, fmt.Errorf("brave search failed (%d): %s", resp.StatusCode, string(body)) } var parsed braveSearchResponse if err := json.Unmarshal(body, &parsed); err != nil { return nil, err } if parsed.Web == nil || len(parsed.Web.Results) == 0 { return []Result{}, nil } results := make([]Result, 0, len(parsed.Web.Results)) for _, item := range parsed.Web.Results { results = append(results, Result{ Title: strings.TrimSpace(item.Title), URL: strings.TrimSpace(item.URL), Description: strings.TrimSpace(item.Description), }) } return results, nil }