Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,29 @@ More hands on examples are available below.
* [Library Github page](https://github.com/serpapi/serpapi-golang)
* [API health status](https://serpapi.com/status)

### Image API

Upload JPG/JPEG, PNG, or WebP image (up to 500 KB) to use with supported search engines.

```golang
setting := serpapi.NewSerpApiClientSetting("<SERPAPI_KEY>")
client := serpapi.NewClient(setting)

upload, err := client.UploadImage("/path/to/image.png")
if err != nil {
panic(err)
}

results, err := client.Search(map[string]string{
"engine": "google_lens",
"image_id": upload["image_id"].(string),
})
```

`UploadImage` also accepts an `io.Reader`. Reader ownership remains with the caller.

Uploaded image IDs expire after 10 minutes. See the [Image API documentation](https://serpapi.com/image-api).

### Location API

```golang
Expand Down
23 changes: 23 additions & 0 deletions README.md.erb
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,29 @@ More hands on examples are available below.
* [Library Github page](https://github.com/serpapi/serpapi-golang)
* [API health status](https://serpapi.com/status)

### Image API

Upload JPG/JPEG, PNG, or WebP image (up to 500 KB) to use with supported search engines.

```golang
setting := serpapi.NewSerpApiClientSetting("<SERPAPI_KEY>")
client := serpapi.NewClient(setting)

upload, err := client.UploadImage("/path/to/image.png")
if err != nil {
panic(err)
}

results, err := client.Search(map[string]string{
"engine": "google_lens",
"image_id": upload["image_id"].(string),
})
```

`UploadImage` also accepts an `io.Reader`. Reader ownership remains with the caller.

Uploaded image IDs expire after 10 minutes. See the [Image API documentation](https://serpapi.com/image-api).

### Location API

```golang
Expand Down
59 changes: 59 additions & 0 deletions serpapi.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package serpapi

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"os"
"time"
)

Expand Down Expand Up @@ -72,6 +75,62 @@ func (client *SerpApiClient) Search(parameter map[string]string) (map[string]int
return client.decodeJSON(rsp.Body)
}

// UploadImage uploads an image to SerpApi's Image API and returns an image_id
// that can be used by search engines such as Google Lens. image may be a file
// path or an io.Reader.
func (client *SerpApiClient) UploadImage(image interface{}) (map[string]interface{}, error) {
rsp, err := client.executeImageUpload(image)
if err != nil {
return nil, err
}
return client.decodeJSON(rsp.Body)
Comment thread
Copilot marked this conversation as resolved.
}

// executeImageUpload sends a multipart POST request to the Image API.
func (client *SerpApiClient) executeImageUpload(image interface{}) (*http.Response, error) {
var reader io.Reader
switch value := image.(type) {
case string:
file, err := os.Open(value)
if err != nil {
return nil, err
}
defer file.Close()
reader = file
case io.Reader:
reader = value
default:
return nil, errors.New("image must be a file path or io.Reader")
}

var body bytes.Buffer
writer := multipart.NewWriter(&body)
part, err := writer.CreateFormFile("image", "image")
if err != nil {
return nil, err
}
if _, err = io.Copy(part, reader); err != nil {
return nil, err
}
Comment thread
tanysheng marked this conversation as resolved.

if client.Setting.SerpApiKey != "" {
if err = writer.WriteField("api_key", client.Setting.SerpApiKey); err != nil {
return nil, err
}
}
if err = writer.Close(); err != nil {
return nil, err
}

request, err := http.NewRequest(http.MethodPost, BaseURL+"/image", &body)
if err != nil {
return nil, err
}
request.Header.Set("Content-Type", writer.FormDataContentType())

return client.HttpSearch.Do(request)
}

// Html returns raw HTML search result
func (client *SerpApiClient) Html(parameter map[string]string) (*string, error) {
rsp, err := client.execute("/search", "html", parameter)
Expand Down
86 changes: 86 additions & 0 deletions test/image_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package serpapi

import (
"io"
"net/http"
"os"
"strings"
"testing"

"github.com/serpapi/serpapi-golang"
)

type roundTripFunc func(*http.Request) (*http.Response, error)

func (function roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) {
return function(request)
}

func imageClient(handler roundTripFunc) serpapi.SerpApiClient {
setting := serpapi.NewSerpApiClientSetting("client-api-key")
client := serpapi.NewClient(setting)
client.HttpSearch = &http.Client{Transport: handler}
return client
}

func TestUploadImage(t *testing.T) {
imagePath := t.TempDir() + string(os.PathSeparator) + "image.png"
if err := os.WriteFile(imagePath, []byte("fake-image-data"), 0600); err != nil {
t.Fatal(err)
}

for name, image := range map[string]interface{}{
"path": imagePath,
"reader": strings.NewReader("fake-image-data"),
} {
t.Run(name, func(t *testing.T) {
client := imageClient(func(request *http.Request) (*http.Response, error) {
if request.Method != http.MethodPost || request.URL.String() != "https://serpapi.com/image" {
t.Errorf("request = %s %s", request.Method, request.URL)
}
if err := request.ParseMultipartForm(1 << 20); err != nil {
t.Fatal(err)
}
if value := request.FormValue("api_key"); value != "client-api-key" {
t.Errorf("api_key = %q, want client-api-key", value)
}
file, _, err := request.FormFile("image")
if err != nil {
t.Fatal(err)
}
defer file.Close()
contents, err := io.ReadAll(file)
if err != nil {
t.Fatal(err)
}
if string(contents) != "fake-image-data" {
t.Errorf("image = %q, want fake-image-data", contents)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(strings.NewReader(`{"image_id":"image-123"}`)),
}, nil
})

result, err := client.UploadImage(image)
if err != nil {
t.Fatal(err)
}
if result["image_id"] != "image-123" {
t.Errorf("image_id = %v, want image-123", result["image_id"])
}
})
}
}

func TestUploadImageRejectsUnsupportedInput(t *testing.T) {
client := imageClient(func(request *http.Request) (*http.Response, error) {
t.Fatal("request should not be sent")
return nil, nil
})

_, err := client.UploadImage(42)
if err == nil || err.Error() != "image must be a file path or io.Reader" {
t.Errorf("error = %v", err)
}
}
Loading