Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,23 @@ exit 0
* source code: [demo/demo.rb](https://github.com/serpapi/serpapi-ruby/blob/master/demo/demo.rb)

## APIs supported
### Image API

Upload a JPG/JPEG, PNG, or WebP image and receive an `image_id` for use with Search API engines that support uploaded images, such as Google Lens. Images must be no larger than 500 KB, and the returned ID expires after 10 minutes.

```ruby
require 'serpapi'

client = SerpApi::Client.new(api_key: ENV['SERPAPI_KEY'])
upload = client.upload_image('/path/to/image.png')
pp upload[:image_id]

results = client.search(engine: 'google_lens', image_id: upload[:image_id])
pp results
```

[See Image API documentation](https://serpapi.com/image-api)

### Location API

```ruby
Expand Down
17 changes: 17 additions & 0 deletions README.md.erb
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,23 @@ exit 0
* source code: [demo/demo.rb](https://github.com/serpapi/serpapi-ruby/blob/master/demo/demo.rb)

## APIs supported
### Image API

Upload a JPG/JPEG, PNG, or WebP image and receive an `image_id` for use with Search API engines that support uploaded images, such as Google Lens. Images must be no larger than 500 KB, and the returned ID expires after 10 minutes.

```ruby
require 'serpapi'

client = SerpApi::Client.new(api_key: ENV['SERPAPI_KEY'])
upload = client.upload_image('/path/to/image.png')
pp upload[:image_id]

results = client.search(engine: 'google_lens', image_id: upload[:image_id])
pp results
```

[See Image API documentation](https://serpapi.com/image-api)

### Location API

```ruby
Expand Down
45 changes: 37 additions & 8 deletions lib/serpapi/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module SerpApi
# * async non-block search
# * persistent HTTP connection
# * search API
# * image API
# * location API
# * account API
# * search archive API
Expand Down Expand Up @@ -134,6 +135,25 @@ def location(params = {})
get('/locations.json', :json, params)
end

# Upload an image using the Image API.
# The returned image ID can be supplied to Search API engines that support
# uploaded images, such as Google Lens. Image IDs expire after 10 minutes.
#
# doc: https://serpapi.com/image-api
#
# @param [String, Pathname, IO] image path or readable image stream
# @param [Hash] params request parameters, such as an API key overriding the client default
# @return [Hash] upload result containing the image ID
def upload_image(image, params = {})
if image.is_a?(String) || (defined?(Pathname) && image.is_a?(Pathname))
::File.open(image, 'rb') do |file|
post('/image', :json, params, image: HTTP::FormData::File.new(file))
end
else
post('/image', :json, params, image: HTTP::FormData::File.new(image))
Comment thread
tanysheng marked this conversation as resolved.
Outdated
end
end
Comment thread
tanysheng marked this conversation as resolved.

# Retrieve search result from the Search Archive API
#
# ```ruby
Expand Down Expand Up @@ -215,17 +235,26 @@ def persistent?
# @param [Hash] params custom search inputs
# @return [String|Hash] raw HTML or decoded response as JSON / Hash
def get(endpoint, decoder = :json, params = {})
response = execute_request(endpoint, params)
response = execute_request(:get, endpoint, params: query(params))
handle_response(response, decoder, endpoint, params)
end

def execute_request(endpoint, params)
if persistent?
@socket.get(endpoint, params: query(params))
else
url = "https://#{BACKEND}#{endpoint}"
HTTP.timeout(timeout).get(url, params: query(params))
end
# Perform an HTTP POST request with a multipart/form-data body
#
# @param [String] endpoint relative SerpApi endpoint, such as "/image"
# @param [Symbol] decoder response decoder, either :json or :html
# @param [Hash] params request parameters
# @param [Hash] form multipart-specific fields, including uploaded files
# @return [String|Hash] raw HTML or decoded response as JSON / Hash
def post(endpoint, decoder = :json, params = {}, form = {})
response = execute_request(:post, endpoint, form: query(params).merge(form))
handle_response(response, decoder, endpoint, params)
end
Comment thread
tanysheng marked this conversation as resolved.

def execute_request(method, endpoint, options)
client = persistent? ? @socket : HTTP.timeout(timeout)
url = persistent? ? endpoint : "https://#{BACKEND}#{endpoint}"
client.public_send(method, url, **options)
end

def handle_response(response, decoder, endpoint, params)
Expand Down
64 changes: 64 additions & 0 deletions spec/serpapi/client/image_api_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
require 'spec_helper'
require 'stringio'
require 'tempfile'

describe 'Image API' do
let(:response) do
double(
status: 200,
body: '{"message":"Image uploaded successfully.","image_id":"test-image-id"}'
)
end
let(:socket) { double }
let(:client) { SerpApi::Client.new(api_key: api_key) }

before do
allow(HTTP).to receive(:persistent).and_return(socket)
allow(response).to receive(:flush)
end

it 'uploads an image from a file path' do
Tempfile.create(['image', '.png']) do |image|
expect(socket).to receive(:post) do |endpoint, options|
uploaded_image = options[:form][:image]

expect(endpoint).to eq('/image')
expect(uploaded_image.filename).to end_with('.png')
response
end
Comment thread
tanysheng marked this conversation as resolved.

result = client.upload_image(image.path)

expect(result[:message]).to eq('Image uploaded successfully.')
expect(result[:image_id]).to eq('test-image-id')
end
end

it 'uploads an image from an IO object' do
image = StringIO.new('image data')

expect(socket).to receive(:post) do |endpoint, options|
expect(endpoint).to eq('/image')
response
end

result = client.upload_image(image)

expect(result[:image_id]).to eq('test-image-id')
end

it 'raises an error when image is rejected' do
error_response = double(
status: 400,
body: '{"error":"Invalid image format. Supported format: jpg, jpeg, png, webp"}'
)
allow(socket).to receive(:post).with('/image', anything).and_return(error_response)

expect {
client.upload_image(StringIO.new('invalid image data'))
}.to raise_error(
SerpApi::SerpApiError,
/Invalid image format\. Supported format: jpg, jpeg, png, webp/
)
end
end
Loading