screenshotscout 0.1.0.rc1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 33024a0bca4f826f2c6c4fe20c6d8d7e9fe744b060b71bfb9f92b8ee99563127
4
+ data.tar.gz: '097bc01284677214c5adeee702b90f31b26240446520e838f87c295e220eddd6'
5
+ SHA512:
6
+ metadata.gz: cd4bd68c1234dccd18df205d029722dec9ee862f614af686e0823d13c345ad6ce6194ccb4bdc4a4cd7fa28c6f05b2daa59651d47314c7e0c05b135acaa8eaf11
7
+ data.tar.gz: 683c78838871e5175676bbac26a02d14a7ade33f4e70a4fa6f9074e9ea634697989e09fc7d8b086bcb5c0d56b142f837a6d79644e1e6fbe10e2ff0b1df4f2905
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Oleksii Velykyi
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,286 @@
1
+ # Screenshot Scout Ruby SDK
2
+
3
+ The official Ruby SDK for the [Screenshot Scout](https://screenshotscout.com/) screenshot API.
4
+
5
+ Capture website screenshots from Ruby applications.
6
+
7
+ ## Requirements
8
+
9
+ - Ruby 3.4 or newer
10
+
11
+ ## Installation
12
+
13
+ Add the gem to your bundle:
14
+
15
+ ```shell
16
+ bundle add screenshotscout
17
+ ```
18
+
19
+ Or install it directly:
20
+
21
+ ```shell
22
+ gem install screenshotscout
23
+ ```
24
+
25
+ ## Get your API credentials
26
+
27
+ [Sign up for Screenshot Scout](https://screenshotscout.com/auth/signup) or sign in, then open the
28
+ [API Keys page](https://screenshotscout.com/app/api-keys). Copy the access key and secret key and
29
+ store them securely. The access key is required. The secret key is optional and enables
30
+ [signed requests](#signed-requests).
31
+
32
+ The examples below read `SCREENSHOTSCOUT_ACCESS_KEY` and, when available,
33
+ `SCREENSHOTSCOUT_SECRET_KEY` from environment variables.
34
+
35
+ ## Capture a screenshot
36
+
37
+ Create a client and call `capture` with the page URL and any capture options. This example captures
38
+ the full page and saves the returned image:
39
+
40
+ ```ruby
41
+ require "screenshotscout"
42
+
43
+ client = ScreenshotScout::Client.new(
44
+ access_key: ENV.fetch("SCREENSHOTSCOUT_ACCESS_KEY"),
45
+ secret_key: ENV.fetch("SCREENSHOTSCOUT_SECRET_KEY", nil)
46
+ )
47
+ response = client.capture(
48
+ "https://example.com",
49
+ ScreenshotScout::CaptureOptions.new(full_page: true)
50
+ )
51
+
52
+ File.binwrite("screenshot.png", response.bytes)
53
+ puts response.screenshot_url
54
+ ```
55
+
56
+ `capture` uses POST by default and returns a `BinaryCaptureResponse` when `response_type` is
57
+ omitted or set to `CaptureResponseType::BINARY`.
58
+
59
+ ## Request a JSON result
60
+
61
+ Set `response_type` to `CaptureResponseType::JSON` to receive capture metadata instead of binary
62
+ image or PDF output:
63
+
64
+ ```ruby
65
+ require "screenshotscout"
66
+
67
+ client = ScreenshotScout::Client.new(
68
+ access_key: ENV.fetch("SCREENSHOTSCOUT_ACCESS_KEY"),
69
+ secret_key: ENV.fetch("SCREENSHOTSCOUT_SECRET_KEY", nil)
70
+ )
71
+ options = ScreenshotScout::CaptureOptions.new(
72
+ response_type: ScreenshotScout::CaptureResponseType::JSON
73
+ )
74
+ response = client.capture("https://example.com", options)
75
+
76
+ puts response.result.screenshot_url
77
+ ```
78
+
79
+ Unrecognized JSON result fields are retained in `response.result.additional_fields`.
80
+
81
+ ## Use GET
82
+
83
+ POST is used by default. Pass `CaptureHttpMethod::GET` when you need a GET request:
84
+
85
+ ```ruby
86
+ require "screenshotscout"
87
+
88
+ client = ScreenshotScout::Client.new(
89
+ access_key: ENV.fetch("SCREENSHOTSCOUT_ACCESS_KEY"),
90
+ secret_key: ENV.fetch("SCREENSHOTSCOUT_SECRET_KEY", nil)
91
+ )
92
+ options = ScreenshotScout::CaptureOptions.new(format: ScreenshotScout::CaptureFormat::WEBP)
93
+ response = client.capture(
94
+ "https://example.com",
95
+ options,
96
+ method: ScreenshotScout::CaptureHttpMethod::GET
97
+ )
98
+
99
+ File.binwrite("screenshot.webp", response.bytes)
100
+ ```
101
+
102
+ ## Build a capture URL
103
+
104
+ Use `build_capture_url` when a browser, an HTML `<img>` element, or another application needs to
105
+ load the screenshot directly. It creates the URL without sending an API request:
106
+
107
+ ```ruby
108
+ require "screenshotscout"
109
+
110
+ client = ScreenshotScout::Client.new(
111
+ access_key: ENV.fetch("SCREENSHOTSCOUT_ACCESS_KEY"),
112
+ secret_key: ENV.fetch("SCREENSHOTSCOUT_SECRET_KEY", nil)
113
+ )
114
+ options = ScreenshotScout::CaptureOptions.new(full_page: true, block_ads: true)
115
+ capture_url = client.build_capture_url("https://example.com", options)
116
+ puts capture_url
117
+ ```
118
+
119
+ The generated URL contains the access key. Treat it as sensitive. Before exposing generated URLs
120
+ to browsers or users, configure a secret key and enable **Require signed requests** for the API key
121
+ on the [API Keys page](https://screenshotscout.com/app/api-keys).
122
+
123
+ ## Signed requests
124
+
125
+ Pass the API key's secret key to sign GET requests, POST requests, and generated capture URLs
126
+ automatically. The secret is used locally and is never transmitted.
127
+
128
+ ```ruby
129
+ require "screenshotscout"
130
+
131
+ client = ScreenshotScout::Client.new(
132
+ access_key: ENV.fetch("SCREENSHOTSCOUT_ACCESS_KEY"),
133
+ secret_key: ENV.fetch("SCREENSHOTSCOUT_SECRET_KEY")
134
+ )
135
+
136
+ response = client.capture("https://example.com")
137
+ File.binwrite("signed-screenshot.png", response.bytes)
138
+ ```
139
+
140
+ See the [signed requests guide](https://screenshotscout.com/docs/signed-requests) for details.
141
+
142
+ ## Capture options
143
+
144
+ The target URL is the first argument to `capture`. Use snake_case keywords to customize the
145
+ capture:
146
+
147
+ - Output: `format`, `response_type`
148
+ - Network and location: `country`, `proxy`, `geolocation_latitude`, `geolocation_longitude`, `geolocation_accuracy`
149
+ - Cookies and webpage headers: `cookies`, `headers`
150
+ - Timing: `timeout`, `wait_until`, `navigation_timeout`, `delay`
151
+ - Device emulation: `device`, `device_viewport_width`, `device_viewport_height`, `device_scale_factor`, `device_is_mobile`, `device_has_touch`, `device_user_agent`
152
+ - Page behavior: `timezone`, `media_type`, `color_scheme`, `reduced_motion`
153
+ - Full page: `full_page`, `full_page_pre_scroll`, `full_page_pre_scroll_step`, `full_page_pre_scroll_step_delay`, `full_page_max_height`
154
+ - Blocking: `block_cookie_banners`, `block_ads`, `block_chat_widgets`
155
+ - DOM changes: `hide_selectors`, `click_selectors`, `click_all_selectors`, `inject_css`, `inject_js`, `bypass_csp`
156
+ - Framing: `selector`, `clip_x`, `clip_y`, `clip_width`, `clip_height`
157
+ - Image output: `image_width`, `image_height`, `image_mode`, `image_anchor`, `image_allow_upscale`, `image_background`, `image_quality`
158
+ - PDF: `pdf_paper_format`, `pdf_landscape`, `pdf_print_background`, `pdf_margin`, `pdf_margin_top`, `pdf_margin_right`, `pdf_margin_bottom`, `pdf_margin_left`, `pdf_scale`
159
+ - Caching: `cache`, `cache_ttl`, `cache_key`
160
+ - Storage: `storage_mode`, `storage_endpoint`, `storage_bucket`, `storage_region`, `storage_object_key`
161
+
162
+ Use the provided constants for documented values, or pass a string:
163
+
164
+ ```ruby
165
+ require "screenshotscout"
166
+
167
+ client = ScreenshotScout::Client.new(
168
+ access_key: ENV.fetch("SCREENSHOTSCOUT_ACCESS_KEY"),
169
+ secret_key: ENV.fetch("SCREENSHOTSCOUT_SECRET_KEY", nil)
170
+ )
171
+ options = ScreenshotScout::CaptureOptions.new(
172
+ format: ScreenshotScout::CaptureFormat::WEBP,
173
+ wait_until: ScreenshotScout::CaptureWaitUntil::LOAD
174
+ )
175
+ response = client.capture("https://example.com", options)
176
+ File.binwrite("screenshot.webp", response.bytes)
177
+ ```
178
+
179
+ Options such as `cookies`, `headers`, selectors, CSS, and JavaScript accept arrays of strings.
180
+
181
+ See the [Screenshot Scout option reference](https://screenshotscout.com/docs/screenshot-options)
182
+ for service behavior and allowed values.
183
+
184
+ ## HTTP timeouts and customization
185
+
186
+ The `timeout` capture option controls how long Screenshot Scout may spend capturing the page. It
187
+ does not configure your application's HTTP timeout.
188
+
189
+ Most applications can use the default `NetHttpTransport`. To customize HTTP timeouts, proxies,
190
+ TLS, or logging, pass an object with a `call(request)` method. It receives a `TransportRequest` and
191
+ returns a `RawResponse`:
192
+
193
+ ```ruby
194
+ require "net/http"
195
+ require "screenshotscout"
196
+ require "uri"
197
+
198
+ class ApplicationTransport
199
+ def initialize(open_timeout:, read_timeout:)
200
+ @open_timeout = open_timeout
201
+ @read_timeout = read_timeout
202
+ end
203
+
204
+ def call(request)
205
+ uri = URI.parse(request.url)
206
+ http_request = build_request(uri, request)
207
+ http = Net::HTTP.new(uri.host, uri.port, nil)
208
+ http.use_ssl = uri.scheme == "https"
209
+ http.open_timeout = @open_timeout
210
+ http.read_timeout = @read_timeout
211
+ http.max_retries = 0
212
+ response = http.start { |session| session.request(http_request) }
213
+
214
+ ScreenshotScout::RawResponse.new(
215
+ status: response.code.to_i,
216
+ reason_phrase: response.message.to_s,
217
+ headers: response.to_hash,
218
+ content_type: response["content-type"],
219
+ body: response.body.to_s.b
220
+ )
221
+ end
222
+
223
+ private
224
+
225
+ def build_request(uri, request)
226
+ http_request = case request.method
227
+ when ScreenshotScout::CaptureHttpMethod::GET
228
+ Net::HTTP::Get.new(uri)
229
+ when ScreenshotScout::CaptureHttpMethod::POST
230
+ Net::HTTP::Post.new(uri)
231
+ else
232
+ raise ArgumentError, "Unsupported HTTP method #{request.method.inspect}."
233
+ end
234
+
235
+ request.headers.each { |name, value| http_request[name] = value }
236
+ http_request.body = request.body unless request.body.nil?
237
+ http_request
238
+ end
239
+ end
240
+
241
+ client = ScreenshotScout::Client.new(
242
+ access_key: ENV.fetch("SCREENSHOTSCOUT_ACCESS_KEY"),
243
+ secret_key: ENV.fetch("SCREENSHOTSCOUT_SECRET_KEY", nil),
244
+ transport: ApplicationTransport.new(open_timeout: 10, read_timeout: 300)
245
+ )
246
+
247
+ response = client.capture("https://example.com")
248
+ File.binwrite("screenshot.png", response.bytes)
249
+ ```
250
+
251
+ ## Raw responses and errors
252
+
253
+ Every successful response exposes `raw_response`, containing the HTTP status, reason phrase,
254
+ headers, content type, and body. `APIError` provides the same response details.
255
+
256
+ ```ruby
257
+ require "screenshotscout"
258
+
259
+ client = ScreenshotScout::Client.new(
260
+ access_key: ENV.fetch("SCREENSHOTSCOUT_ACCESS_KEY"),
261
+ secret_key: ENV.fetch("SCREENSHOTSCOUT_SECRET_KEY", nil)
262
+ )
263
+
264
+ begin
265
+ client.capture("https://example.com")
266
+ puts "Capture succeeded."
267
+ rescue ScreenshotScout::APIError => error
268
+ warn [error.status, error.error_code, error.error_message].inspect
269
+ warn error.errors.inspect
270
+ warn error.response_body.inspect if error.response_body?
271
+ warn error.raw_response.body.inspect
272
+ rescue ScreenshotScout::TransportError => error
273
+ warn error.cause.inspect
274
+ rescue ScreenshotScout::ConfigurationError,
275
+ ScreenshotScout::SerializationError,
276
+ ScreenshotScout::ResponseDecodingError => error
277
+ warn error.message
278
+ end
279
+ ```
280
+
281
+ All SDK exceptions inherit from `ScreenshotScout::Error`, which you can rescue to handle any SDK
282
+ error.
283
+
284
+ ## License
285
+
286
+ Licensed under the [MIT License](LICENSE).
@@ -0,0 +1,43 @@
1
+ # Examples
2
+
3
+ - [`binary.rb`](binary.rb) captures a full-page screenshot, saves it as `screenshot.png`, and
4
+ prints the returned screenshot URL.
5
+ - [`json.rb`](json.rb) requests a JSON response and prints the screenshot URL.
6
+ - [`capture_url.rb`](capture_url.rb) creates and prints a capture URL without sending an API
7
+ request.
8
+
9
+ ## Run the examples
10
+
11
+ From the repository root, install the dependencies:
12
+
13
+ ```shell
14
+ bundle install
15
+ ```
16
+
17
+ Set your access key. Set the optional secret key to use
18
+ [signed requests](../README.md#signed-requests) and signed capture URLs. The secret key is required
19
+ when **Require signed requests** is enabled for the API key.
20
+
21
+ PowerShell:
22
+
23
+ ```powershell
24
+ $env:SCREENSHOTSCOUT_ACCESS_KEY = "YOUR_ACCESS_KEY"
25
+ $env:SCREENSHOTSCOUT_SECRET_KEY = "YOUR_SECRET_KEY"
26
+ ```
27
+
28
+ macOS or Linux:
29
+
30
+ ```shell
31
+ export SCREENSHOTSCOUT_ACCESS_KEY="YOUR_ACCESS_KEY"
32
+ export SCREENSHOTSCOUT_SECRET_KEY="YOUR_SECRET_KEY"
33
+ ```
34
+
35
+ Run any example:
36
+
37
+ ```shell
38
+ bundle exec ruby -Ilib examples/binary.rb
39
+ bundle exec ruby -Ilib examples/json.rb
40
+ bundle exec ruby -Ilib examples/capture_url.rb
41
+ ```
42
+
43
+ Generated capture URLs contain the access key and should be treated as sensitive.
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "screenshotscout"
4
+
5
+ access_key = ENV.fetch("SCREENSHOTSCOUT_ACCESS_KEY")
6
+ secret_key = ENV.fetch("SCREENSHOTSCOUT_SECRET_KEY", nil)
7
+ client = ScreenshotScout::Client.new(access_key: access_key, secret_key: secret_key)
8
+ options = ScreenshotScout::CaptureOptions.new(full_page: true)
9
+ response = client.capture("https://example.com", options)
10
+
11
+ File.binwrite("screenshot.png", response.bytes)
12
+ puts response.screenshot_url
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "screenshotscout"
4
+
5
+ access_key = ENV.fetch("SCREENSHOTSCOUT_ACCESS_KEY")
6
+ secret_key = ENV.fetch("SCREENSHOTSCOUT_SECRET_KEY", nil)
7
+ client = ScreenshotScout::Client.new(access_key: access_key, secret_key: secret_key)
8
+ options = ScreenshotScout::CaptureOptions.new(full_page: true, block_ads: true)
9
+
10
+ puts client.build_capture_url("https://example.com", options)
data/examples/json.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "screenshotscout"
4
+
5
+ access_key = ENV.fetch("SCREENSHOTSCOUT_ACCESS_KEY")
6
+ secret_key = ENV.fetch("SCREENSHOTSCOUT_SECRET_KEY", nil)
7
+ client = ScreenshotScout::Client.new(access_key: access_key, secret_key: secret_key)
8
+ options = ScreenshotScout::CaptureOptions.new(
9
+ response_type: ScreenshotScout::CaptureResponseType::JSON
10
+ )
11
+ response = client.capture("https://example.com", options)
12
+
13
+ puts response.result.screenshot_url
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScreenshotScout
4
+ # Keyword-initialized options for one Screenshot Scout capture.
5
+ class CaptureOptions
6
+ ATTRIBUTE_NAMES = %i[
7
+ format response_type country proxy geolocation_latitude geolocation_longitude
8
+ geolocation_accuracy cookies headers timeout wait_until navigation_timeout delay
9
+ device device_viewport_width device_viewport_height device_scale_factor device_is_mobile
10
+ device_has_touch device_user_agent timezone media_type color_scheme reduced_motion
11
+ full_page full_page_pre_scroll full_page_pre_scroll_step full_page_pre_scroll_step_delay
12
+ full_page_max_height block_cookie_banners block_ads block_chat_widgets hide_selectors
13
+ click_selectors click_all_selectors inject_css inject_js bypass_csp selector clip_x clip_y
14
+ clip_width clip_height image_width image_height image_mode image_anchor image_allow_upscale
15
+ image_background image_quality pdf_paper_format pdf_landscape pdf_print_background
16
+ pdf_margin pdf_margin_top pdf_margin_right pdf_margin_bottom pdf_margin_left pdf_scale cache
17
+ cache_ttl cache_key storage_mode storage_endpoint storage_bucket storage_region
18
+ storage_object_key
19
+ ].freeze
20
+
21
+ attr_reader(*ATTRIBUTE_NAMES)
22
+
23
+ def initialize(**options)
24
+ unknown = options.keys - ATTRIBUTE_NAMES
25
+ unless unknown.empty?
26
+ option = unknown.first
27
+ raise SerializationError.new("Unknown capture option \"#{option}\".", option: option)
28
+ end
29
+
30
+ ATTRIBUTE_NAMES.each do |name|
31
+ instance_variable_set("@#{name}", options[name])
32
+ end
33
+ end
34
+
35
+ private_constant :ATTRIBUTE_NAMES
36
+ end
37
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScreenshotScout
4
+ module CaptureHttpMethod
5
+ GET = "GET"
6
+ POST = "POST"
7
+ end
8
+
9
+ module CaptureFormat
10
+ GIF = "gif"
11
+ JPEG = "jpeg"
12
+ JPG = "jpg"
13
+ PDF = "pdf"
14
+ PNG = "png"
15
+ TIFF = "tiff"
16
+ WEBP = "webp"
17
+ end
18
+
19
+ module CaptureResponseType
20
+ BINARY = "binary"
21
+ JSON = "json"
22
+ end
23
+
24
+ module CaptureWaitUntil
25
+ DOM_CONTENT_LOADED = "domcontentloaded"
26
+ LOAD = "load"
27
+ NETWORK_IDLE_0 = "networkidle0"
28
+ NETWORK_IDLE_2 = "networkidle2"
29
+ end
30
+
31
+ module CaptureMediaType
32
+ PRINT = "print"
33
+ SCREEN = "screen"
34
+ end
35
+
36
+ module CaptureColorScheme
37
+ AUTO = "auto"
38
+ DARK = "dark"
39
+ LIGHT = "light"
40
+ end
41
+
42
+ module CaptureImageMode
43
+ FILL = "fill"
44
+ FIT = "fit"
45
+ STRETCH = "stretch"
46
+ end
47
+
48
+ module CaptureImageAnchor
49
+ BOTTOM = "bottom"
50
+ BOTTOM_LEFT = "bottom_left"
51
+ BOTTOM_RIGHT = "bottom_right"
52
+ CENTER = "center"
53
+ LEFT = "left"
54
+ RIGHT = "right"
55
+ TOP = "top"
56
+ TOP_LEFT = "top_left"
57
+ TOP_RIGHT = "top_right"
58
+ end
59
+
60
+ module CapturePdfPaperFormat
61
+ A3 = "a3"
62
+ A4 = "a4"
63
+ CONTENT = "content"
64
+ LEGAL = "legal"
65
+ LETTER = "letter"
66
+ TABLOID = "tabloid"
67
+ end
68
+
69
+ module CaptureStorageMode
70
+ EXTERNAL = "external"
71
+ MANAGED = "managed"
72
+ end
73
+ end