From 345818b16340ce593dbdf05b61e314bf213339fe Mon Sep 17 00:00:00 2001 From: John Pedrie Date: Fri, 3 May 2024 15:58:39 -0400 Subject: [PATCH] add support for new summaries and product clusters (#9) --- brave.go | 51 ++++++++++++++++++++++++++++++++++----- summary.go | 49 +++++++++++++++++++++++++++++++++++-- types.go | 71 +++++++++++++++++++++++++++++++++++++++++++++++++----- web.go | 7 ++++-- 4 files changed, 162 insertions(+), 16 deletions(-) diff --git a/brave.go b/brave.go index fb9cbf9..bba41d6 100644 --- a/brave.go +++ b/brave.go @@ -9,12 +9,13 @@ import ( ) const ( - defaultBaseURL = "https://api.search.brave.com/res/v1/" - imageSearchPath = "images/search" - spellcheckPath = "spellcheck/search" - suggestSearchPath = "suggest/search" - videoSearchPath = "videos/search" - webSearchPath = "web/search" + defaultBaseURL = "https://api.search.brave.com/res/v1/" + imageSearchPath = "images/search" + spellcheckPath = "spellcheck/search" + suggestSearchPath = "suggest/search" + videoSearchPath = "videos/search" + webSearchPath = "web/search" + summarizerSearchPath = "summarizer/search" ) // Brave is an interface for fetching results from the Brave Search API. @@ -215,6 +216,8 @@ type searchOptions struct { units UnitType userAgent string apiVersion string + summary bool + entityInfo bool } func (s searchOptions) getFreshness() string { @@ -646,6 +649,10 @@ func WithLocPostalCode(v string) SearchOption { } // WithSpellcheck toggles spellchecking on or off. +// +// Refer to [Query Parameters] for more detail. +// +// [Query Parameters]: https://api.search.brave.com/app/documentation/query func WithSpellcheck(v bool) SearchOption { return func(o searchOptions) searchOptions { o.spellcheck = &v @@ -654,6 +661,10 @@ func WithSpellcheck(v bool) SearchOption { } // WithAPIVersion specifies the version of the API to use for a request. +// +// Refer to [Query Headers] for more detail. +// +// [Query Headers]: https://api.search.brave.com/app/documentation/headers func WithAPIVersion(v string) SearchOption { return func(o searchOptions) searchOptions { o.apiVersion = v @@ -661,6 +672,34 @@ func WithAPIVersion(v string) SearchOption { } } +// WithSummary specifies whether a summary key should be requested. +// +// Applicable to [Brave.WebSearch]. +// +// Refer to [Query Parameters] for more detail. +// +// [Query Parameters]: https://api.search.brave.com/app/documentation/query +func WithSummary(v bool) SearchOption { + return func(o searchOptions) searchOptions { + o.summary = v + return o + } +} + +// WithEntityInfo specifies whether entity information should be returned +// +// Applicable to [Brave.SummarizerSearch]. +// +// Refer to [Query Parameters] for more detail. +// +// [Query Parameters]: https://api.search.brave.com/app/documentation/query +func WithEntityInfo(v bool) SearchOption { + return func(o searchOptions) searchOptions { + o.entityInfo = v + return o + } +} + // ClientOption allows configuration of the API client. type ClientOption func(clientOptions) clientOptions diff --git a/summary.go b/summary.go index 961ab1c..a0cac58 100644 --- a/summary.go +++ b/summary.go @@ -2,9 +2,54 @@ package brave import ( "context" - "errors" + "net/http" + + "github.com/google/go-querystring/query" ) func (b *brave) SummarizerSearch(ctx context.Context, key string, options ...SearchOption) (*SummarizerSearchResult, error) { - return nil, errors.New("not implemented") + u := *b.baseURL + u.Path = u.Path + summarizerSearchPath + + var opts searchOptions + applyOpts(&opts, options, nil) + + var params summarizerSearchParams + params.fromSearchOptions(key, opts) + + values, err := query.Values(params) + if err != nil { + return nil, err + } + + u.RawQuery = values.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + if err != nil { + return nil, err + } + + opts.applyRequestHeaders(b.subscriptionToken, req) + + return handleRequest[SummarizerSearchResult](b.client, req) +} + +type SummarizerSearchResult struct { + Type string `json:"type"` + Status string `json:"status"` + Title string `json:"title"` + Summary []SummaryMessage `json:"summary"` + Enrichments *SummaryEnrichments `json:"enrichments"` + Followups []string `json:"followups"` + EntitiesInfo map[string]any `json:"entities_info"` +} + +type summarizerSearchParams struct { + Key string `url:"key"` + EntityInfo bool `url:"entity_info"` +} + +func (s *summarizerSearchParams) fromSearchOptions(key string, options searchOptions) { + s.Key = key + s.EntityInfo = options.entityInfo } diff --git a/types.go b/types.go index d9e32cf..50a5cd0 100644 --- a/types.go +++ b/types.go @@ -171,7 +171,7 @@ type SearchResult struct { Rating *Rating `json:"rating"` Article *Article `json:"article"` // Product any `json:"product"` - // ProductCluster []any `json:"product_cluster"` + ProductCluster []Product `json:"product_cluster"` ClusterType string `json:"cluster_type"` Cluster []Result `json:"cluster"` CreativeWork *CreativeWork `json:"creative_work"` @@ -239,6 +239,7 @@ type Image struct { Thumbnail *Thumbnail `json:"thumbnail"` URL string `json:"url"` Properties *ImageProperties `json:"properties"` + Text string `json:"text"` } type ImageProperties struct { @@ -408,15 +409,15 @@ type Price struct { type Article struct { Author []Person `json:"author"` Date string `json:"date"` - Publisher *Organization `json:"organization"` + Publisher *Organization `json:"publisher"` Thumbnail *Thumbnail `json:"thumbnail"` IsAccessibleForFree bool `json:"isAccessibleForFree"` } type Organization struct { - Person - - Type string `json:"type"` + Type string `json:"type"` + Name string `json:"name"` + Thumbnail *Thumbnail `json:"thumbnail"` } type CreativeWork struct { @@ -450,7 +451,49 @@ type Software struct { IsPyPi bool `json:"is_pypi"` } -type SummarizerSearchResult struct{} +type Summarizer struct { + Type string `json:"type"` + Key string `json:"key"` +} + +type SummaryMessage struct { + Type string `json:"type"` + Data string `json:"data"` +} + +type SummaryEnrichments struct { + Raw string `json:"raw"` + Images []Image `json:"images"` + QA []SummaryAnswer `json:"qa"` + Entities []SummaryEntity `json:"entities"` + Context []SummaryContext `json:"context"` +} + +type SummaryAnswer struct { + Answer string `json:"answer"` + Score float32 `json:"score"` + Highlight *TextLocation `json:"highlight"` +} + +type SummaryEntity struct { + UUID string `json:"uuid"` + Name string `json:"name"` + URL string `json:"url"` + Text string `json:"text"` + Images []Image `json:"images"` + Highlight []TextLocation `json:"highlight"` +} + +type SummaryContext struct { + Title string `json:"title"` + URL string `json:"url"` + MetaURL *MetaURL `json:"meta_url"` +} + +type TextLocation struct { + Start Number `json:"start"` + End Number `json:"end"` +} type SuggestResult struct { Query string `json:"string"` @@ -499,6 +542,22 @@ type GraphInfoBox struct { Movie *MovieData `json:"movie"` } +type Product struct { + Type string `json:"type"` + Name string `json:"name"` + Price string `json:"price"` + Thumbnail *Thumbnail `json:"thumbnail"` + Description string `json:"description"` + Offers []Offer `json:"offers"` + Rating *Rating `json:"rating"` +} + +type Offer struct { + URL string `json:"url"` + Price string `json:"price"` + PriceCurrency string `json:"priceCurrency"` +} + type ErrorResponse struct { ID string `json:"id"` Status int `json:"status"` diff --git a/web.go b/web.go index ee78dbd..90de60a 100644 --- a/web.go +++ b/web.go @@ -37,14 +37,15 @@ func (b *brave) WebSearch(ctx context.Context, term string, options ...SearchOpt type WebSearchResult struct { Type string `json:"type"` Discussions *ResultContainer[DiscussionResult] `json:"discussions"` - FAQ any `json:"faq"` + FAQ *ResultContainer[QA] `json:"faq"` InfoBox *ResultContainer[GraphInfoBox] `json:"infobox"` - Locations *Locations `json:"locations"` + Locations *ResultContainer[LocationResult] `json:"locations"` Mixed *Mixed `json:"mixed"` News *ResultContainer[NewsResult] `json:"news"` Query *Query `json:"query"` Videos *ResultContainer[VideoResult] `json:"videos"` Web *ResultContainer[SearchResult] `json:"web"` + Summarizer *Summarizer `json:"summarizer"` } type webSearchParams struct { @@ -62,6 +63,7 @@ type webSearchParams struct { TextDecorations bool `url:"text_decorations,omitempty"` UILang string `url:"ui_lang,omitempty"` Units string `url:"units,omitempty"` + Summary bool `url:"summary,omitempty"` } func (w *webSearchParams) fromSearchOptions(term string, options searchOptions) { @@ -79,4 +81,5 @@ func (w *webSearchParams) fromSearchOptions(term string, options searchOptions) w.TextDecorations = options.textDecorations w.UILang = options.uiLang w.Units = options.units.String() + w.Summary = options.summary }