screenshotapi_to 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +6 -0
- data/LICENSE +21 -0
- data/README.md +230 -0
- data/examples/plain_ruby.rb +29 -0
- data/examples/rails_controller.rb +35 -0
- data/lib/screenshotapi/client.rb +195 -0
- data/lib/screenshotapi/errors.rb +44 -0
- data/lib/screenshotapi/result.rb +21 -0
- data/lib/screenshotapi/version.rb +3 -0
- data/lib/screenshotapi.rb +9 -0
- metadata +89 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c1c1d419da719dd316e4191a2e504bacc39397c41c9e88a2c65f3208f72a4f3d
|
|
4
|
+
data.tar.gz: 2ee900fd3711e56949e0c5d77beacf600bed5662341b5293123a059e18305bf2
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 31f16655ac4f7c5211fc7d6adf0a1d2b94b9f1423f321597ff8e8b0da8ccf352922d78e2799214e96c054f228cd044cd61f705eef04fd7705f31f22c7fd8f227
|
|
7
|
+
data.tar.gz: 8690139a987d24acb4f4b77f43f2f547212e8180a5557309c2d00dcc59dd06a334f1de3b9440e68b83af028bb2c88bf7dbc93de95f8d0b73d033b32d3533ab3b
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ScreenshotAPI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# screenshotapi_to
|
|
2
|
+
|
|
3
|
+
Official Ruby SDK for [ScreenshotAPI](https://screenshotapi.to?utm_source=ruby_sdk&utm_medium=readme&utm_campaign=sdk&ref=ruby-sdk). Capture website screenshots, PDFs, and rendered page states with a small `net/http` client and no runtime dependencies.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add the gem to your Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem "screenshotapi_to", require: "screenshotapi"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then install:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install directly:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
gem install screenshotapi_to
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Authentication
|
|
26
|
+
|
|
27
|
+
Create a free ScreenshotAPI account and copy an API key from the dashboard:
|
|
28
|
+
|
|
29
|
+
- [Get an API key](https://screenshotapi.to/sign-up?utm_source=ruby_sdk&utm_medium=readme&utm_campaign=sdk&ref=ruby-sdk)
|
|
30
|
+
- [API documentation](https://screenshotapi.to/docs?utm_source=ruby_sdk&utm_medium=readme&utm_campaign=sdk&ref=ruby-sdk)
|
|
31
|
+
|
|
32
|
+
Keep the key on the server and load it from an environment variable:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
export SCREENSHOTAPI_KEY="sk_live_your_key_here"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
require "screenshotapi"
|
|
40
|
+
|
|
41
|
+
client = ScreenshotAPI::Client.new(ENV.fetch("SCREENSHOTAPI_KEY"))
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## First Screenshot
|
|
45
|
+
|
|
46
|
+
Capture a PNG and save it to disk:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
require "screenshotapi"
|
|
50
|
+
|
|
51
|
+
client = ScreenshotAPI::Client.new(ENV.fetch("SCREENSHOTAPI_KEY"))
|
|
52
|
+
|
|
53
|
+
metadata = client.save(
|
|
54
|
+
url: "https://example.com",
|
|
55
|
+
path: "screenshot.png"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
puts "Screenshot ID: #{metadata.screenshot_id}"
|
|
59
|
+
puts "Credits remaining: #{metadata.credits_remaining}"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Use `screenshot` when you need the raw bytes:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
result = client.screenshot(url: "https://example.com", type: "webp")
|
|
66
|
+
|
|
67
|
+
File.binwrite("screenshot.webp", result.image)
|
|
68
|
+
puts result.content_type
|
|
69
|
+
puts result.metadata.duration_ms
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Advanced Options
|
|
73
|
+
|
|
74
|
+
All GET-compatible screenshot options can be passed as Ruby keyword arguments. The client converts snake_case keys to ScreenshotAPI query parameters.
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
result = client.screenshot(
|
|
78
|
+
url: "https://example.com/pricing",
|
|
79
|
+
width: 1440,
|
|
80
|
+
height: 1200,
|
|
81
|
+
full_page: true,
|
|
82
|
+
type: "webp",
|
|
83
|
+
quality: 85,
|
|
84
|
+
color_scheme: "dark",
|
|
85
|
+
wait_until: "networkidle2",
|
|
86
|
+
wait_for_selector: "main",
|
|
87
|
+
delay: 500,
|
|
88
|
+
block_ads: true,
|
|
89
|
+
remove_cookie_banners: true,
|
|
90
|
+
stealth_mode: true,
|
|
91
|
+
device_pixel_ratio: 2,
|
|
92
|
+
timezone: "America/New_York",
|
|
93
|
+
locale: "en-US",
|
|
94
|
+
cache_ttl: 300,
|
|
95
|
+
preload_fonts: true,
|
|
96
|
+
remove_elements: [".newsletter", "#cookie-banner"],
|
|
97
|
+
remove_popups: true
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
File.binwrite("pricing.webp", result.image)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Render raw HTML with `html:`. This uses `POST /api/v1/screenshot` with a JSON body:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
result = client.screenshot(
|
|
107
|
+
html: "<main><h1>Hello from Ruby</h1></main>",
|
|
108
|
+
width: 800,
|
|
109
|
+
height: 600,
|
|
110
|
+
type: "png"
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Generate a PDF:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
metadata = client.save(
|
|
118
|
+
url: "https://example.com/report",
|
|
119
|
+
type: "pdf",
|
|
120
|
+
path: "report.pdf"
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## API Reference
|
|
125
|
+
|
|
126
|
+
### `ScreenshotAPI::Client.new(api_key, base_url:, timeout:)`
|
|
127
|
+
|
|
128
|
+
| Parameter | Type | Required | Default | Description |
|
|
129
|
+
| --- | --- | --- | --- | --- |
|
|
130
|
+
| `api_key` | `String` | Yes | - | Your ScreenshotAPI key. |
|
|
131
|
+
| `base_url` | `String` | No | `https://screenshotapi.to` | API base URL. |
|
|
132
|
+
| `timeout` | `Integer` | No | `60` | Open and read timeout in seconds. |
|
|
133
|
+
|
|
134
|
+
### `client.screenshot(**options)`
|
|
135
|
+
|
|
136
|
+
Returns `ScreenshotAPI::Result`.
|
|
137
|
+
|
|
138
|
+
| Option | Type | Required | Default | Description |
|
|
139
|
+
| --- | --- | --- | --- | --- |
|
|
140
|
+
| `url` | `String` | Yes, unless `html` is provided | - | Absolute `http` or `https` URL to capture. |
|
|
141
|
+
| `html` | `String` | No | - | Raw HTML to render. Uses POST. |
|
|
142
|
+
| `width` | `Integer` | No | `1440` | Viewport width in pixels. |
|
|
143
|
+
| `height` | `Integer` | No | `900` | Viewport height in pixels. |
|
|
144
|
+
| `full_page` | `Boolean` | No | `false` | Capture the full scrollable page. |
|
|
145
|
+
| `type` | `String` | No | `"png"` | `"png"`, `"jpeg"`, `"webp"`, or `"pdf"`. |
|
|
146
|
+
| `quality` | `Integer` | No | `100` | JPEG/WebP quality from `1` to `100`. |
|
|
147
|
+
| `color_scheme` | `String` | No | - | `"light"` or `"dark"`. |
|
|
148
|
+
| `wait_until` | `String` | No | `"networkidle2"` | `"load"`, `"domcontentloaded"`, `"networkidle0"`, or `"networkidle2"`. |
|
|
149
|
+
| `wait_for_selector` | `String` | No | - | CSS selector to wait for before capture. |
|
|
150
|
+
| `delay` | `Integer` | No | `0` | Additional delay in milliseconds. |
|
|
151
|
+
| `block_ads` | `Boolean` | No | `false` | Remove ads before capture. |
|
|
152
|
+
| `remove_cookie_banners` | `Boolean` | No | `false` | Auto-remove common cookie consent dialogs. |
|
|
153
|
+
| `css_inject` | `String` | No | - | CSS to inject before capture. |
|
|
154
|
+
| `js_inject` | `String` | No | - | JavaScript to run before capture. |
|
|
155
|
+
| `stealth_mode` | `Boolean` | No | `false` | Enable anti-bot-detection mode. |
|
|
156
|
+
| `device_pixel_ratio` | `Integer` | No | `1` | Retina/HiDPI scale. Accepted values: `1`, `2`, `3`. |
|
|
157
|
+
| `timezone` | `String` | No | - | IANA timezone, such as `"America/New_York"`. |
|
|
158
|
+
| `locale` | `String` | No | - | BCP 47 locale, such as `"en-US"`. |
|
|
159
|
+
| `cache_ttl` | `Integer` | No | `0` | Response cache TTL in seconds. |
|
|
160
|
+
| `preload_fonts` | `Boolean` | No | `false` | Preload discovered Google Fonts before capture. |
|
|
161
|
+
| `remove_elements` | `Array<String>` | No | - | CSS selectors to remove before capture. |
|
|
162
|
+
| `remove_popups` | `Boolean` | No | `false` | Remove common popups and overlays. |
|
|
163
|
+
| `mockup_device` | `String` | No | - | `"browser"`, `"iphone"`, or `"macbook"`. |
|
|
164
|
+
| `geo_latitude`, `geo_longitude`, `geo_accuracy` | `Number` | No | - | Browser geolocation override for GET requests. |
|
|
165
|
+
|
|
166
|
+
### `client.save(path:, **options)`
|
|
167
|
+
|
|
168
|
+
Same options as `screenshot`, plus `path:`. Writes the response body to disk and returns `ScreenshotAPI::Metadata`.
|
|
169
|
+
|
|
170
|
+
## Error Handling
|
|
171
|
+
|
|
172
|
+
The SDK raises typed errors for API responses and network failures:
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
require "screenshotapi"
|
|
176
|
+
|
|
177
|
+
client = ScreenshotAPI::Client.new(ENV.fetch("SCREENSHOTAPI_KEY"))
|
|
178
|
+
|
|
179
|
+
begin
|
|
180
|
+
result = client.screenshot(url: "https://example.com")
|
|
181
|
+
File.binwrite("screenshot.png", result.image)
|
|
182
|
+
rescue ScreenshotAPI::AuthenticationError
|
|
183
|
+
warn "API key missing or malformed"
|
|
184
|
+
rescue ScreenshotAPI::InvalidAPIKeyError
|
|
185
|
+
warn "API key revoked or invalid"
|
|
186
|
+
rescue ScreenshotAPI::InsufficientCreditsError => e
|
|
187
|
+
warn "No credits remaining. Balance: #{e.balance}"
|
|
188
|
+
rescue ScreenshotAPI::ScreenshotFailedError => e
|
|
189
|
+
warn "Screenshot capture failed: #{e.message}"
|
|
190
|
+
rescue ScreenshotAPI::NetworkError => e
|
|
191
|
+
warn "Network error: #{e.message}"
|
|
192
|
+
rescue ScreenshotAPI::APIError => e
|
|
193
|
+
warn "ScreenshotAPI error #{e.status}: #{e.message}"
|
|
194
|
+
end
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Examples
|
|
198
|
+
|
|
199
|
+
Runnable examples live in `examples/`:
|
|
200
|
+
|
|
201
|
+
- `examples/plain_ruby.rb` captures a screenshot from a plain Ruby script.
|
|
202
|
+
- `examples/rails_controller.rb` shows a Rails controller action that returns screenshot bytes with typed error responses.
|
|
203
|
+
|
|
204
|
+
Run the plain Ruby example with:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
SCREENSHOTAPI_KEY="sk_live_your_key_here" ruby examples/plain_ruby.rb
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Pricing And Free Tier
|
|
211
|
+
|
|
212
|
+
New accounts include 200 free screenshots per month. Paid plans support higher monthly volume, credit packs, caching, webhooks, S3 upload, signed URLs, and priority support.
|
|
213
|
+
|
|
214
|
+
- [Start free](https://screenshotapi.to/sign-up?utm_source=ruby_sdk&utm_medium=readme&utm_campaign=sdk&ref=ruby-sdk)
|
|
215
|
+
- [View pricing](https://screenshotapi.to/pricing?utm_source=ruby_sdk&utm_medium=readme&utm_campaign=sdk&ref=ruby-sdk)
|
|
216
|
+
|
|
217
|
+
## Documentation And Support
|
|
218
|
+
|
|
219
|
+
- [Ruby SDK documentation](https://screenshotapi.to/docs/sdks/ruby?utm_source=ruby_sdk&utm_medium=readme&utm_campaign=sdk&ref=ruby-sdk)
|
|
220
|
+
- [Screenshot API reference](https://screenshotapi.to/docs/api/screenshot?utm_source=ruby_sdk&utm_medium=readme&utm_campaign=sdk&ref=ruby-sdk)
|
|
221
|
+
- [Email support](mailto:support@screenshotapi.to)
|
|
222
|
+
|
|
223
|
+
## Requirements
|
|
224
|
+
|
|
225
|
+
- Ruby 3.0+
|
|
226
|
+
- No runtime gem dependencies. The client uses `net/http`, `json`, and `uri` from the Ruby standard library.
|
|
227
|
+
|
|
228
|
+
## License
|
|
229
|
+
|
|
230
|
+
MIT
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require "screenshotapi"
|
|
2
|
+
|
|
3
|
+
api_key = ENV["SCREENSHOTAPI_KEY"]
|
|
4
|
+
abort "Set SCREENSHOTAPI_KEY before running this example." if api_key.nil? || api_key.strip.empty?
|
|
5
|
+
|
|
6
|
+
client = ScreenshotAPI::Client.new(api_key)
|
|
7
|
+
|
|
8
|
+
begin
|
|
9
|
+
metadata = client.save(
|
|
10
|
+
url: "https://example.com",
|
|
11
|
+
path: "example.png",
|
|
12
|
+
width: 1440,
|
|
13
|
+
height: 900,
|
|
14
|
+
full_page: true,
|
|
15
|
+
type: "png",
|
|
16
|
+
block_ads: true,
|
|
17
|
+
remove_cookie_banners: true
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
puts "Saved example.png"
|
|
21
|
+
puts "Screenshot ID: #{metadata.screenshot_id}"
|
|
22
|
+
puts "Credits remaining: #{metadata.credits_remaining}"
|
|
23
|
+
rescue ScreenshotAPI::InsufficientCreditsError => e
|
|
24
|
+
warn "ScreenshotAPI credits exhausted. Balance: #{e.balance}"
|
|
25
|
+
exit 1
|
|
26
|
+
rescue ScreenshotAPI::APIError => e
|
|
27
|
+
warn "ScreenshotAPI request failed (#{e.code}, HTTP #{e.status || "n/a"}): #{e.message}"
|
|
28
|
+
exit 1
|
|
29
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require "screenshotapi"
|
|
2
|
+
|
|
3
|
+
class ScreenshotsController < ApplicationController
|
|
4
|
+
def show
|
|
5
|
+
result = screenshot_client.screenshot(
|
|
6
|
+
url: params.require(:url),
|
|
7
|
+
width: params.fetch(:width, 1440).to_i,
|
|
8
|
+
height: params.fetch(:height, 900).to_i,
|
|
9
|
+
type: params.fetch(:type, "png"),
|
|
10
|
+
full_page: ActiveModel::Type::Boolean.new.cast(params[:full_page]),
|
|
11
|
+
block_ads: true,
|
|
12
|
+
remove_cookie_banners: true
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
expires_in 1.hour, public: true
|
|
16
|
+
send_data result.image, type: result.content_type, disposition: "inline"
|
|
17
|
+
rescue ActionController::ParameterMissing
|
|
18
|
+
render json: { error: "url is required" }, status: :bad_request
|
|
19
|
+
rescue ScreenshotAPI::InsufficientCreditsError => e
|
|
20
|
+
render json: { error: "insufficient credits", balance: e.balance }, status: :payment_required
|
|
21
|
+
rescue ScreenshotAPI::AuthenticationError, ScreenshotAPI::InvalidAPIKeyError
|
|
22
|
+
render json: { error: "ScreenshotAPI authentication failed" }, status: :unauthorized
|
|
23
|
+
rescue ScreenshotAPI::APIError => e
|
|
24
|
+
Rails.logger.warn("ScreenshotAPI failed: #{e.code} #{e.message}")
|
|
25
|
+
render json: { error: "screenshot capture failed" }, status: :bad_gateway
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def screenshot_client
|
|
31
|
+
@screenshot_client ||= ScreenshotAPI::Client.new(
|
|
32
|
+
Rails.application.credentials.dig(:screenshotapi, :api_key)
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
require "net/http"
|
|
2
|
+
require "uri"
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module ScreenshotAPI
|
|
6
|
+
class Client
|
|
7
|
+
QUERY_PARAM_MAP = {
|
|
8
|
+
url: "url",
|
|
9
|
+
width: "width",
|
|
10
|
+
height: "height",
|
|
11
|
+
full_page: "fullPage",
|
|
12
|
+
type: "type",
|
|
13
|
+
quality: "quality",
|
|
14
|
+
color_scheme: "colorScheme",
|
|
15
|
+
wait_until: "waitUntil",
|
|
16
|
+
wait_for_selector: "waitForSelector",
|
|
17
|
+
delay: "delay",
|
|
18
|
+
block_ads: "blockAds",
|
|
19
|
+
remove_cookie_banners: "removeCookieBanners",
|
|
20
|
+
css_inject: "cssInject",
|
|
21
|
+
js_inject: "jsInject",
|
|
22
|
+
stealth_mode: "stealthMode",
|
|
23
|
+
device_pixel_ratio: "devicePixelRatio",
|
|
24
|
+
timezone: "timezone",
|
|
25
|
+
locale: "locale",
|
|
26
|
+
cache_ttl: "cacheTtl",
|
|
27
|
+
preload_fonts: "preloadFonts",
|
|
28
|
+
remove_elements: "removeElements",
|
|
29
|
+
remove_popups: "removePopups",
|
|
30
|
+
mockup_device: "mockupDevice",
|
|
31
|
+
geo_latitude: "geoLatitude",
|
|
32
|
+
geo_longitude: "geoLongitude",
|
|
33
|
+
geo_accuracy: "geoAccuracy"
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
BODY_PARAM_MAP = QUERY_PARAM_MAP.merge(
|
|
37
|
+
html: "html",
|
|
38
|
+
geo_location: "geoLocation"
|
|
39
|
+
).freeze
|
|
40
|
+
|
|
41
|
+
def initialize(api_key, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT)
|
|
42
|
+
raise ArgumentError, "API key is required" if blank?(api_key)
|
|
43
|
+
|
|
44
|
+
@api_key = api_key.to_s
|
|
45
|
+
@base_url = normalize_base_url(base_url)
|
|
46
|
+
@timeout = timeout
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def screenshot(**options)
|
|
50
|
+
validate_capture_target!(options)
|
|
51
|
+
|
|
52
|
+
uri, request = build_request(options)
|
|
53
|
+
request["x-api-key"] = @api_key
|
|
54
|
+
|
|
55
|
+
response = perform_request(uri, request)
|
|
56
|
+
|
|
57
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
58
|
+
handle_error(response)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
metadata = Metadata.new(
|
|
62
|
+
credits_remaining: integer_header(response, "x-credits-remaining"),
|
|
63
|
+
screenshot_id: response["x-screenshot-id"] || "",
|
|
64
|
+
duration_ms: integer_header(response, "x-duration-ms")
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
Result.new(
|
|
68
|
+
image: response.body,
|
|
69
|
+
content_type: response["content-type"] || "image/png",
|
|
70
|
+
metadata: metadata
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def save(path:, **options)
|
|
75
|
+
result = screenshot(**options)
|
|
76
|
+
File.binwrite(path, result.image)
|
|
77
|
+
result.metadata
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def perform_request(uri, request)
|
|
83
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
84
|
+
http.use_ssl = uri.scheme == "https"
|
|
85
|
+
http.open_timeout = @timeout
|
|
86
|
+
http.read_timeout = @timeout
|
|
87
|
+
|
|
88
|
+
http.request(request)
|
|
89
|
+
rescue Timeout::Error, SocketError, SystemCallError, IOError => e
|
|
90
|
+
raise NetworkError, e.message
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def build_uri(options)
|
|
94
|
+
params = build_params(options, QUERY_PARAM_MAP)
|
|
95
|
+
|
|
96
|
+
uri = URI("#{@base_url}/api/v1/screenshot")
|
|
97
|
+
uri.query = URI.encode_www_form(params)
|
|
98
|
+
uri
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def build_request(options)
|
|
102
|
+
if present?(options[:html])
|
|
103
|
+
uri = URI("#{@base_url}/api/v1/screenshot")
|
|
104
|
+
request = Net::HTTP::Post.new(uri)
|
|
105
|
+
request["content-type"] = "application/json"
|
|
106
|
+
request.body = JSON.generate(build_body(options))
|
|
107
|
+
[uri, request]
|
|
108
|
+
else
|
|
109
|
+
uri = build_uri(options)
|
|
110
|
+
[uri, Net::HTTP::Get.new(uri)]
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def build_body(options)
|
|
115
|
+
build_params(options, BODY_PARAM_MAP, stringify: false)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def build_params(options, mapping, stringify: true)
|
|
119
|
+
mapping.each_with_object({}) do |(option_key, param_key), params|
|
|
120
|
+
next unless options.key?(option_key)
|
|
121
|
+
|
|
122
|
+
value = normalize_param_value(option_key, options[option_key], stringify: stringify)
|
|
123
|
+
next if value.nil?
|
|
124
|
+
|
|
125
|
+
params[param_key] = stringify ? value.to_s : value
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def normalize_param_value(option_key, value, stringify:)
|
|
130
|
+
return nil if value.nil?
|
|
131
|
+
return value.join(",") if stringify && option_key == :remove_elements && value.respond_to?(:join)
|
|
132
|
+
|
|
133
|
+
value
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def handle_error(response)
|
|
137
|
+
body = parse_error_body(response)
|
|
138
|
+
|
|
139
|
+
message = body["error"] || body["message"] || "HTTP #{response.code}"
|
|
140
|
+
|
|
141
|
+
case response.code.to_i
|
|
142
|
+
when 401
|
|
143
|
+
raise AuthenticationError, message
|
|
144
|
+
when 402
|
|
145
|
+
balance = body.key?("balance") ? body["balance"] : body["creditBalance"]
|
|
146
|
+
raise InsufficientCreditsError.new(message, balance: integer_value(balance))
|
|
147
|
+
when 403
|
|
148
|
+
raise InvalidAPIKeyError, message
|
|
149
|
+
when 500
|
|
150
|
+
raise ScreenshotFailedError, (body["message"] || body["error"] || "Screenshot failed")
|
|
151
|
+
else
|
|
152
|
+
raise APIError.new(message, status: response.code.to_i, code: "unknown_error")
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def parse_error_body(response)
|
|
157
|
+
parsed = JSON.parse(response.body.to_s)
|
|
158
|
+
parsed.is_a?(Hash) ? parsed : { "error" => "HTTP #{response.code}" }
|
|
159
|
+
rescue JSON::ParserError
|
|
160
|
+
{ "error" => "HTTP #{response.code}" }
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def integer_header(response, header)
|
|
164
|
+
integer_value(response[header])
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def integer_value(value)
|
|
168
|
+
return 0 if value.nil? || value.to_s.empty?
|
|
169
|
+
|
|
170
|
+
value.to_i
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def validate_capture_target!(options)
|
|
174
|
+
return if present?(options[:html])
|
|
175
|
+
return if present?(options[:url])
|
|
176
|
+
|
|
177
|
+
raise ArgumentError, "URL or HTML is required"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def normalize_base_url(base_url)
|
|
181
|
+
value = base_url.to_s.strip
|
|
182
|
+
raise ArgumentError, "base_url is required" if value.empty?
|
|
183
|
+
|
|
184
|
+
value.sub(%r{/+\z}, "")
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def blank?(value)
|
|
188
|
+
!present?(value)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def present?(value)
|
|
192
|
+
!value.nil? && !value.to_s.strip.empty?
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module ScreenshotAPI
|
|
2
|
+
class APIError < StandardError
|
|
3
|
+
attr_reader :status, :code
|
|
4
|
+
|
|
5
|
+
def initialize(message, status:, code:)
|
|
6
|
+
super(message)
|
|
7
|
+
@status = status
|
|
8
|
+
@code = code
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class AuthenticationError < APIError
|
|
13
|
+
def initialize(message)
|
|
14
|
+
super(message, status: 401, code: "authentication_error")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class InsufficientCreditsError < APIError
|
|
19
|
+
attr_reader :balance
|
|
20
|
+
|
|
21
|
+
def initialize(message, balance: 0)
|
|
22
|
+
super(message, status: 402, code: "insufficient_credits")
|
|
23
|
+
@balance = balance
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class InvalidAPIKeyError < APIError
|
|
28
|
+
def initialize(message)
|
|
29
|
+
super(message, status: 403, code: "invalid_api_key")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class ScreenshotFailedError < APIError
|
|
34
|
+
def initialize(message)
|
|
35
|
+
super(message, status: 500, code: "screenshot_failed")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class NetworkError < APIError
|
|
40
|
+
def initialize(message)
|
|
41
|
+
super(message, status: nil, code: "network_error")
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module ScreenshotAPI
|
|
2
|
+
class Metadata
|
|
3
|
+
attr_reader :credits_remaining, :screenshot_id, :duration_ms
|
|
4
|
+
|
|
5
|
+
def initialize(credits_remaining:, screenshot_id:, duration_ms:)
|
|
6
|
+
@credits_remaining = credits_remaining
|
|
7
|
+
@screenshot_id = screenshot_id
|
|
8
|
+
@duration_ms = duration_ms
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class Result
|
|
13
|
+
attr_reader :image, :content_type, :metadata
|
|
14
|
+
|
|
15
|
+
def initialize(image:, content_type:, metadata:)
|
|
16
|
+
@image = image
|
|
17
|
+
@content_type = content_type
|
|
18
|
+
@metadata = metadata
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
require_relative "screenshotapi/version"
|
|
2
|
+
require_relative "screenshotapi/client"
|
|
3
|
+
require_relative "screenshotapi/errors"
|
|
4
|
+
require_relative "screenshotapi/result"
|
|
5
|
+
|
|
6
|
+
module ScreenshotAPI
|
|
7
|
+
DEFAULT_BASE_URL = "https://screenshotapi.to"
|
|
8
|
+
DEFAULT_TIMEOUT = 60
|
|
9
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: screenshotapi_to
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- ScreenshotAPI
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-06-29 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: minitest
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '5.16'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '5.16'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '13.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '13.0'
|
|
41
|
+
description: Capture website screenshots with the ScreenshotAPI service. Simple, fast,
|
|
42
|
+
and reliable.
|
|
43
|
+
email:
|
|
44
|
+
- support@screenshotapi.to
|
|
45
|
+
executables: []
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- CHANGELOG.md
|
|
50
|
+
- LICENSE
|
|
51
|
+
- README.md
|
|
52
|
+
- examples/plain_ruby.rb
|
|
53
|
+
- examples/rails_controller.rb
|
|
54
|
+
- lib/screenshotapi.rb
|
|
55
|
+
- lib/screenshotapi/client.rb
|
|
56
|
+
- lib/screenshotapi/errors.rb
|
|
57
|
+
- lib/screenshotapi/result.rb
|
|
58
|
+
- lib/screenshotapi/version.rb
|
|
59
|
+
homepage: https://screenshotapi.to
|
|
60
|
+
licenses:
|
|
61
|
+
- MIT
|
|
62
|
+
metadata:
|
|
63
|
+
allowed_push_host: https://rubygems.org
|
|
64
|
+
bug_tracker_uri: https://github.com/miketromba/screenshotapi-ruby/issues
|
|
65
|
+
changelog_uri: https://github.com/miketromba/screenshotapi-ruby/blob/main/CHANGELOG.md
|
|
66
|
+
documentation_uri: https://screenshotapi.to/docs/sdks/ruby
|
|
67
|
+
homepage_uri: https://screenshotapi.to
|
|
68
|
+
rubygems_mfa_required: 'true'
|
|
69
|
+
source_code_uri: https://github.com/miketromba/screenshotapi-ruby
|
|
70
|
+
post_install_message:
|
|
71
|
+
rdoc_options: []
|
|
72
|
+
require_paths:
|
|
73
|
+
- lib
|
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
75
|
+
requirements:
|
|
76
|
+
- - ">="
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: 3.0.0
|
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - ">="
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: '0'
|
|
84
|
+
requirements: []
|
|
85
|
+
rubygems_version: 3.5.22
|
|
86
|
+
signing_key:
|
|
87
|
+
specification_version: 4
|
|
88
|
+
summary: Official Ruby SDK for ScreenshotAPI
|
|
89
|
+
test_files: []
|