bannerbear 0.1.4 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a63e7f3269afc788dea97a56ba271553cce7ae8ad8c605246321be788bbe5601
4
- data.tar.gz: 68aa7455b8b4b71ddcf47ee0a59563d0a36f437670822680b9030ff0d41993a2
3
+ metadata.gz: daa1514b2c5a5dddbef5836f7d64e3863932f4204e95a9625e7577b49dc70cad
4
+ data.tar.gz: 7a33d8e33c07d6b39561e6fbf6b64defafbb7d593a777bd1524f7623528e861c
5
5
  SHA512:
6
- metadata.gz: 2e4cc99311a703073a184a5211bf1c7c14b18df1062b03592558368e4d017d3f5e116f1c7948c63703300bb4af6cc6da5c0b15bdaf5942e4a1465624fc89c0af
7
- data.tar.gz: a3017ce751ebfac3260bd7f174b61040ac2c0130a28f13ced336f7d77e0944507a195ef28aba8701d706fc4b6bef8d5b428a7ee3e51bfa716ef92fddf33af26a
6
+ metadata.gz: 20c731f7f32fab1d5afc6fc6f85134febaacd2a386b3e86c526a0c2134a099272f8d98560ca469129f3300286db7439ff8f374e8bd62d1217c985a23a770d06f
7
+ data.tar.gz: df3b49048aa06fb412a3715228ae2af463701267636d00cdf8a7ede033c0a77e6d1c3bd7229cfb84fe1aef3ed1d79123741e23240cedeaa4895b096ed2bd34e2
data/.gitignore CHANGED
@@ -6,3 +6,4 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ /smoke_test_v5.rb
data/Gemfile.lock CHANGED
@@ -1,19 +1,21 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bannerbear (0.1.4)
4
+ bannerbear (0.2.0)
5
5
  httparty
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- httparty (0.20.0)
11
- mime-types (~> 3.0)
10
+ bigdecimal (4.1.2)
11
+ csv (3.3.5)
12
+ httparty (0.24.2)
13
+ csv
14
+ mini_mime (>= 1.0.0)
12
15
  multi_xml (>= 0.5.2)
13
- mime-types (3.4.1)
14
- mime-types-data (~> 3.2015)
15
- mime-types-data (3.2022.0105)
16
- multi_xml (0.6.0)
16
+ mini_mime (1.1.5)
17
+ multi_xml (0.9.1)
18
+ bigdecimal (>= 3.1, < 5)
17
19
  rake (12.3.3)
18
20
 
19
21
  PLATFORMS
data/README.md CHANGED
@@ -18,6 +18,364 @@ Or install it yourself as:
18
18
 
19
19
  $ gem install bannerbear
20
20
 
21
+ ## V5 API
22
+
23
+ The [V5 API](https://developers.bannerbear.com/v5/) is a new generation of the Bannerbear API. **V5 API keys do not work with V2 endpoints, and V2 API keys do not work with V5 endpoints** — you must use the right client class for your key.
24
+
25
+ For the **V5 API**, use `Bannerbear::V5::Client` (this section).
26
+ For the **legacy V2 API**, see [Usage](#usage) below — that section is unchanged.
27
+
28
+ ### Table of Contents
29
+
30
+ - [Authentication (V5)](#authentication-v5)
31
+ - [Account (V5)](#account-v5)
32
+ - [Image Templates (V5)](#image-templates-v5)
33
+ - [Images (V5)](#images-v5)
34
+ - [Tools (V5)](#tools-v5)
35
+ - [Assets (V5)](#assets-v5)
36
+ - [Publications (V5)](#publications-v5)
37
+ - [Batches (V5)](#batches-v5)
38
+ - [Webhooks (V5)](#webhooks-v5)
39
+ - [Instant URLs (V5)](#instant-urls-v5)
40
+
41
+ ### Authentication (V5)
42
+
43
+ ```ruby
44
+ bb = Bannerbear::V5::Client.new("your V5 API key")
45
+ ```
46
+
47
+ Or set `BANNERBEAR_API_KEY` and call without arguments:
48
+
49
+ ```ruby
50
+ bb = Bannerbear::V5::Client.new
51
+ ```
52
+
53
+ ### Account (V5)
54
+
55
+ ```ruby
56
+ bb.account
57
+ ```
58
+
59
+ ### Image Templates (V5)
60
+
61
+ V5 renames V2's `templates` resource to `image_templates`. Templates can be created, updated, and deleted through the API — `config` holds the full canvas.
62
+
63
+ ```ruby
64
+ bb.list_image_templates(page: 1)
65
+ bb.get_image_template("template uid")
66
+
67
+ bb.create_image_template(
68
+ name: "My Template",
69
+ description: "Created from the API",
70
+ tags: ["portrait"],
71
+ width: 1080,
72
+ height: 1080,
73
+ config: { objects: [
74
+ { id: "bg", type: "rectangle", left: 0, top: 0, width: 1080, height: 1080, "background-color" => "#0f172a" },
75
+ { id: "headline", type: "text", left: 80, top: 400, width: 920, text: "Hello World!", "font-size" => 72, color: "#ffffff" }
76
+ ] }
77
+ )
78
+
79
+ bb.update_image_template("template uid", name: "New Name", description: "...", tags: ["portrait"])
80
+ bb.delete_image_template("template uid")
81
+ ```
82
+
83
+ ##### Options for `create_image_template` / `update_image_template`
84
+
85
+ - `name` *required for create* (`string`)
86
+ - `description` (`string`)
87
+ - `tags` (`array`)
88
+ - `width` / `height`: canvas size in pixels (`integer`)
89
+ - `config`: full canvas configuration, `{ objects: [...] }`. Passing it **replaces** the existing config in place (`hash`)
90
+
91
+ Deleting is a soft delete: images already rendered from the template stay intact, but the template no longer appears in list/get calls and cannot be used for new renders.
92
+
93
+ ### Images (V5)
94
+
95
+ V5's `modifications` is an **object** with two sub-keys:
96
+
97
+ - `template` — template-level changes (width, height, etc.)
98
+ - `objects` — array of per-layer changes (equivalent to V2's flat modifications array)
99
+
100
+ ```ruby
101
+ bb.create_image("template uid",
102
+ modifications: {
103
+ template: { width: 1080, height: 1080 },
104
+ objects: [
105
+ { name: "headline", text: "Hello World!" },
106
+ { name: "photo", image_url: "https://images.unsplash.com/photo-1555400038-63f5ba517a47?w=1000&q=80" }
107
+ ]
108
+ }
109
+ )
110
+ ```
111
+
112
+ Synchronous generation routes to `sync.api.bannerbear.com/v5` (10s timeout). The `sync:` flag is a Ruby-level switch — it is **not** sent in the request body:
113
+
114
+ ```ruby
115
+ bb.create_image("template uid", sync: true, modifications: { objects: [...] })
116
+ ```
117
+
118
+ ##### Options for `create_image`
119
+
120
+ - `modifications`: V5 modifications object (`hash`)
121
+ - `formats`: output formats, e.g. `["jpg", "pdf"]` (`array`)
122
+ - `scale`: scale multiplier, 1–4 (`integer`)
123
+ - `dpi`: DPI metadata (`integer`)
124
+ - `quality`: quality control (`integer`)
125
+ - `proxy`: proxy and resize external images before rendering (`boolean`)
126
+ - `metadata`: include any metadata to reference at a later point (`string`)
127
+ - `version`: pin template version (`integer`)
128
+ - `sync`: route to the sync host (`boolean`; Ruby-only, not sent to the API)
129
+
130
+ ```ruby
131
+ bb.get_image("image uid")
132
+ bb.list_images(page: 1)
133
+ ```
134
+
135
+ ### Tools (V5)
136
+
137
+ Tools are standalone media operations that do not use a template. Every tool is **asynchronous**: the call returns a pending *tool job*. Poll `get_tool_job` until the status is `"completed"` or `"failed"`, or subscribe to a webhook with the resource `"tool_job"`.
138
+
139
+ ```ruby
140
+ job = bb.trim_video(video_url: "https://example.com/clip.mp4", start: 2.5, end: 10.0)
141
+
142
+ job = bb.get_tool_job(job["uid"])
143
+ job["status"] # => "pending" | "running" | "completed" | "failed"
144
+ job["outputs"]["video_url"] if job["status"] == "completed"
145
+
146
+ bb.list_tool_jobs(page: 1)
147
+ ```
148
+
149
+ Every tool also accepts an optional `metadata` string.
150
+
151
+ | Method | Required | Optional | Output key |
152
+ | --- | --- | --- | --- |
153
+ | `remove_bg` | `image_url` | — | `image_url` |
154
+ | `create_pdf` | `urls` | — | `pdf_url` |
155
+ | `trim_video` | `video_url`, `start`, `end` | — | `video_url` |
156
+ | `concat_videos` | `video_urls` | `width`, `height` | `video_url` |
157
+ | `resize_video` | `video_url`, `width`, `height` | `fit` | `video_url` |
158
+ | `crop_video` | `video_url`, `x`, `y`, `width`, `height` | — | `video_url` |
159
+ | `overlay_video` | `base_video_url`, `overlay_video_url`, `x`, `y` | `scale`, `start` | `video_url` |
160
+ | `overlay_image` | `video_url`, `image_url`, `x`, `y` | `opacity` | `video_url` |
161
+ | `subtitle_video` | `video_url` | `language`, `font`, `font_size`, `color`, `bold`, `italic`, `outline_color`, `outline_width`, `shadow_size`, `shadow_color`, `background_style`, `background_color`, `alignment` | `video_url` |
162
+ | `generate_voiceover` | `text`, `voice` | — | `audio_url` |
163
+ | `add_audio` | `video_url`, `audio_url`, `mode` | `volume`, `loop`, `ducking` | `video_url` |
164
+ | `add_cover_art` | `video_url`, `image_url` | — | `video_url` |
165
+ | `create_video_slideshow` | `image_urls` | `slide_duration`, `transition`, `transition_duration`, `width`, `height` | `video_url` |
166
+ | `apply_color_filter` | `video_url`, `filter` | — | `video_url` |
167
+ | `soften_video` | `video_url`, `strength` | — | `video_url` |
168
+
169
+ A few examples:
170
+
171
+ ```ruby
172
+ bb.remove_bg(image_url: "https://example.com/product.png")
173
+
174
+ bb.subtitle_video(
175
+ video_url: "https://example.com/talk.mp4",
176
+ font: "montserrat",
177
+ font_size: 32,
178
+ color: "#ffffff",
179
+ background_style: "outline",
180
+ alignment: "2"
181
+ )
182
+
183
+ bb.generate_voiceover(text: "Welcome to Bannerbear.", voice: "rachel")
184
+
185
+ bb.create_video_slideshow(
186
+ image_urls: ["https://example.com/1.jpg", "https://example.com/2.jpg"],
187
+ slide_duration: 3,
188
+ transition: "fade",
189
+ width: 1280,
190
+ height: 720
191
+ )
192
+ ```
193
+
194
+ `create_tool_job` calls any tool by name — the escape hatch for tools added after this release:
195
+
196
+ ```ruby
197
+ bb.create_tool_job("remove_bg", image_url: "https://example.com/product.png")
198
+ ```
199
+
200
+ ### Assets (V5)
201
+
202
+ Upload a file (max 5MB) and get back a durable CDN URL you can feed to image modifications or tools. Uploads are deduplicated per workspace by SHA-256, so re-uploading the same bytes returns the existing record instead of creating a duplicate.
203
+
204
+ ```ruby
205
+ asset = bb.upload_asset(File.binread("logo.png"), "image/png")
206
+ asset["url"]
207
+
208
+ bb.get_asset("asset uid")
209
+ bb.list_assets(page: 1)
210
+ ```
211
+
212
+ Accepted mime types: `image/jpeg`, `image/png`, `image/webp`, `image/gif`, `video/mp4`, `video/webm`, `video/quicktime`, `audio/mpeg`, `audio/wav`, `audio/mp4`, `audio/webm`, `audio/ogg`, `application/pdf`.
213
+
214
+ `check_assets` maps each SHA-256 content hash to its existing asset (or `nil`), so a syncing client can skip the upload round-trip for content it already pushed. Max 100 hashes per call.
215
+
216
+ ```ruby
217
+ digest = OpenSSL::Digest::SHA256.hexdigest(File.binread("logo.png"))
218
+ found = bb.check_assets([digest])
219
+ bb.upload_asset(File.binread("logo.png"), "image/png") if found[digest].nil?
220
+ ```
221
+
222
+ ### Publications (V5)
223
+
224
+ Publications are templates published to the public library. Installing one clones it into your workspace as a new image template.
225
+
226
+ ```ruby
227
+ bb.list_publications(page: 1)
228
+ bb.get_publication("publication uid")
229
+
230
+ template = bb.install_publication("publication uid")
231
+ template["uid"]
232
+ ```
233
+
234
+ ### Batches (V5)
235
+
236
+ Generate multiple images in one request (up to 100).
237
+
238
+ ```ruby
239
+ bb.create_batch(
240
+ type: "images",
241
+ items: [
242
+ { template: "template uid 1", modifications: { objects: [...] } },
243
+ { template: "template uid 2", modifications: { objects: [...] } }
244
+ ]
245
+ )
246
+ bb.get_batch("batch uid")
247
+ bb.list_batches(page: 1)
248
+ ```
249
+
250
+ ### Webhooks (V5)
251
+
252
+ Webhooks are managed as a first-class resource in V5 (instead of being a per-request `webhook_url` parameter).
253
+
254
+ ```ruby
255
+ hook = bb.create_webhook(
256
+ name: "my-webhook",
257
+ url: "https://example.com/hook",
258
+ resource: "image",
259
+ event: "completed",
260
+ status: "active",
261
+ scope: "all_templates",
262
+ templates: []
263
+ )
264
+
265
+ # IMPORTANT: signing_key is ONLY returned in the create response. Store it now —
266
+ # subsequent get_webhook calls will not include it.
267
+ puts hook["signing_key"]
268
+ ```
269
+
270
+ ##### Options for `create_webhook` / `update_webhook`
271
+
272
+ - `name` *required* (`string`)
273
+ - `url` *required* — the URL that receives the events (`string`)
274
+ - `resource`: `"image"`, `"batch"`, or `"tool_job"` (`string`)
275
+ - `event`: `"all_events"`, `"completed"`, or `"failed"` (`string`)
276
+ - `status`: `"active"` or `"disabled"` (`string`)
277
+ - `scope`: `"all_templates"` or `"specific_templates"` (`string`)
278
+ - `templates`: template UIDs, used when `scope` is `"specific_templates"` (`array`)
279
+
280
+ CRUD:
281
+
282
+ ```ruby
283
+ bb.get_webhook("webhook uid")
284
+ bb.update_webhook("webhook uid",
285
+ name: "renamed",
286
+ url: "https://example.com/hook",
287
+ resource: "image",
288
+ event: "completed",
289
+ status: "active",
290
+ scope: "all_templates"
291
+ )
292
+ bb.delete_webhook("webhook uid")
293
+ bb.list_webhooks(page: 1)
294
+ ```
295
+
296
+ ### Instant URLs (V5)
297
+
298
+ Instant URLs are URLs bound to a template that can be manipulated with query strings — the V5 equivalent of V2's "Signed URLs" feature.
299
+
300
+ #### Create an Instant URL base
301
+
302
+ ```ruby
303
+ iurl = bb.create_instant_url(
304
+ name: "my-instant-url",
305
+ template: "template uid",
306
+ mode: "encoded", # or "named_params"
307
+ security: "signed", # or "open"
308
+ status: "active",
309
+ scale: 1 # 1, 2, 3, or 4
310
+ )
311
+
312
+ # IMPORTANT: signing_key is ONLY returned in the create response. Store it now.
313
+ puts iurl["signing_key"]
314
+ puts iurl["base_url"]
315
+ ```
316
+
317
+ ##### Options for `create_instant_url` / `update_instant_url`
318
+
319
+ - `name` *required* (`string`)
320
+ - `template` *required* — image template UID (`string`)
321
+ - `mode`: `"encoded"` or `"named_params"` (`string`)
322
+ - `security`: `"signed"` or `"open"` (`string`)
323
+ - `status`: `"active"` or `"disabled"` (`string`)
324
+ - `scale`: 1, 2, 3, or 4 (`integer`)
325
+ - `rate_limit`: enable per-IP rate limiting (`boolean`)
326
+ - `template_version`: pin template version (`integer`, nullable)
327
+ - `max_renders`: cap total renders (`integer`, nullable)
328
+ - `expires_at`: ISO 8601 expiry (`string`, nullable)
329
+
330
+ CRUD:
331
+
332
+ ```ruby
333
+ bb.get_instant_url("uid")
334
+ bb.update_instant_url("uid", name: "...", template: "...", ...)
335
+ bb.delete_instant_url("uid")
336
+ bb.list_instant_urls(page: 1)
337
+ ```
338
+
339
+ #### Build an Instant URL with modifications
340
+
341
+ `build_instant_url` is a pure local helper — no API call. It composes the URL from a base + modifications and, if a signing key is provided, appends the HMAC signature.
342
+
343
+ ```ruby
344
+ # Encoded mode, signed
345
+ bb.build_instant_url(iurl["base_url"],
346
+ mode: "encoded",
347
+ signing_key: iurl["signing_key"],
348
+ modifications: {
349
+ template: { width: 1030, height: 890 },
350
+ objects: [{ name: "title", text: "Hello!", color: "#ffffff" }]
351
+ }
352
+ )
353
+
354
+ # Named params mode, signed
355
+ bb.build_instant_url(iurl["base_url"],
356
+ mode: "named_params",
357
+ signing_key: iurl["signing_key"],
358
+ modifications: {
359
+ template: { width: 1030, height: 890 },
360
+ objects: [{ name: "title", text: "Hello!" }]
361
+ }
362
+ )
363
+
364
+ # Open (unsigned): omit signing_key
365
+ bb.build_instant_url(iurl["base_url"],
366
+ mode: "encoded",
367
+ modifications: { objects: [{ name: "title", text: "Hello!" }] }
368
+ )
369
+ ```
370
+
371
+ ##### Options for `build_instant_url`
372
+
373
+ - `mode`: `"encoded"` (default) or `"named_params"` (`string`)
374
+ - `signing_key`: only needed when the instant URL was created with `security: "signed"` (`string`)
375
+ - `modifications`: same shape as `create_image`'s modifications (`hash`)
376
+
377
+ ---
378
+
21
379
  ## Usage
22
380
 
23
381
  ### Table of Contents
@@ -0,0 +1,307 @@
1
+ module Bannerbear
2
+ module V5
3
+
4
+ class Client
5
+
6
+ def initialize(api_key = nil)
7
+ @api_key = api_key || ENV["BANNERBEAR_API_KEY"]
8
+ end
9
+
10
+ def account
11
+ get_response "/account"
12
+ end
13
+
14
+ # Image Templates
15
+
16
+ def list_image_templates(params = {})
17
+ get_response "/image_templates?#{URI.encode_www_form(params.slice(:page))}"
18
+ end
19
+
20
+ def get_image_template(uid)
21
+ get_response "/image_templates/#{uid}"
22
+ end
23
+
24
+ def create_image_template(payload = {})
25
+ post_response "/image_templates", payload.slice(:name, :description, :tags, :width, :height, :config)
26
+ end
27
+
28
+ def update_image_template(uid, payload = {})
29
+ patch_response "/image_templates/#{uid}", payload.slice(:name, :description, :tags, :width, :height, :config)
30
+ end
31
+
32
+ def delete_image_template(uid)
33
+ delete_response "/image_templates/#{uid}"
34
+ end
35
+
36
+ # Images
37
+
38
+ def list_images(params = {})
39
+ get_response "/images?#{URI.encode_www_form(params.slice(:page))}"
40
+ end
41
+
42
+ def get_image(uid)
43
+ get_response "/images/#{uid}"
44
+ end
45
+
46
+ def create_image(uid, payload = {})
47
+ post_response "/images", payload.slice(:modifications, :formats, :scale, :dpi, :quality, :proxy, :metadata, :version).merge({:template => uid}), payload[:sync]
48
+ end
49
+
50
+ # Tools
51
+ #
52
+ # Every tool is asynchronous: the POST returns a pending tool job. Poll
53
+ # get_tool_job until the status is "completed" or "failed", or subscribe
54
+ # to a webhook with the resource "tool_job".
55
+
56
+ TOOL_PARAMS = {
57
+ "remove_bg" => [:image_url],
58
+ "create_pdf" => [:urls],
59
+ "trim_video" => [:video_url, :start, :end],
60
+ "concat_videos" => [:video_urls, :width, :height],
61
+ "resize_video" => [:video_url, :width, :height, :fit],
62
+ "crop_video" => [:video_url, :x, :y, :width, :height],
63
+ "overlay_video" => [:base_video_url, :overlay_video_url, :x, :y, :scale, :start],
64
+ "overlay_image" => [:video_url, :image_url, :x, :y, :opacity],
65
+ "subtitle_video" => [:video_url, :language, :font, :font_size, :color, :bold, :italic,
66
+ :outline_color, :outline_width, :shadow_size, :shadow_color,
67
+ :background_style, :background_color, :alignment],
68
+ "generate_voiceover" => [:text, :voice],
69
+ "add_audio" => [:video_url, :audio_url, :mode, :volume, :loop, :ducking],
70
+ "add_cover_art" => [:video_url, :image_url],
71
+ "create_video_slideshow" => [:image_urls, :slide_duration, :transition, :transition_duration, :width, :height],
72
+ "apply_color_filter" => [:video_url, :filter],
73
+ "soften_video" => [:video_url, :strength]
74
+ }.freeze
75
+
76
+ def create_tool_job(tool, payload = {})
77
+ allowed = TOOL_PARAMS[tool.to_s]
78
+ raise ArgumentError, "unknown tool: #{tool.inspect}" if allowed.nil?
79
+ post_response "/tools/#{tool}", payload.slice(*allowed, :metadata)
80
+ end
81
+
82
+ # Defines one method per tool, e.g. remove_bg(payload) or trim_video(payload).
83
+ TOOL_PARAMS.each_key do |tool|
84
+ define_method(tool) { |payload = {}| create_tool_job(tool, payload) }
85
+ end
86
+
87
+ # Tool Jobs
88
+
89
+ def list_tool_jobs(params = {})
90
+ get_response "/tool_jobs?#{URI.encode_www_form(params.slice(:page))}"
91
+ end
92
+
93
+ def get_tool_job(uid)
94
+ get_response "/tool_jobs/#{uid}"
95
+ end
96
+
97
+ # Assets
98
+
99
+ def list_assets(params = {})
100
+ get_response "/assets?#{URI.encode_www_form(params.slice(:page))}"
101
+ end
102
+
103
+ def get_asset(uid)
104
+ get_response "/assets/#{uid}"
105
+ end
106
+
107
+ # Uploads raw file bytes (max 5MB). content_type must be the mime type of
108
+ # the data, e.g. "image/png" or "video/mp4". Uploads are deduplicated per
109
+ # workspace by SHA-256, so re-uploading the same bytes returns the
110
+ # existing asset instead of creating a duplicate.
111
+ def upload_asset(data, content_type)
112
+ upload_response "/assets", data, content_type
113
+ end
114
+
115
+ # Returns a map of SHA-256 content hash => asset (or nil when the hash is
116
+ # not stored in this workspace). Max 100 hashes per call.
117
+ def check_assets(content_hashes)
118
+ post_response "/assets/check", { :content_hashes => content_hashes }
119
+ end
120
+
121
+ # Publications
122
+
123
+ def list_publications(params = {})
124
+ get_response "/publications?#{URI.encode_www_form(params.slice(:page))}"
125
+ end
126
+
127
+ def get_publication(uid)
128
+ get_response "/publications/#{uid}"
129
+ end
130
+
131
+ # Clones a publication into the workspace as a new image template.
132
+ def install_publication(uid)
133
+ post_response "/publications/#{uid}/install", {}
134
+ end
135
+
136
+ # Batches
137
+
138
+ def list_batches(params = {})
139
+ get_response "/batches?#{URI.encode_www_form(params.slice(:page))}"
140
+ end
141
+
142
+ def get_batch(uid)
143
+ get_response "/batches/#{uid}"
144
+ end
145
+
146
+ def create_batch(payload = {})
147
+ post_response "/batches", payload.slice(:type, :items)
148
+ end
149
+
150
+ # Webhooks
151
+
152
+ def list_webhooks(params = {})
153
+ get_response "/webhooks?#{URI.encode_www_form(params.slice(:page))}"
154
+ end
155
+
156
+ def get_webhook(uid)
157
+ get_response "/webhooks/#{uid}"
158
+ end
159
+
160
+ def create_webhook(payload = {})
161
+ post_response "/webhooks", payload.slice(:name, :url, :resource, :event, :status, :scope, :templates)
162
+ end
163
+
164
+ def update_webhook(uid, payload = {})
165
+ patch_response "/webhooks/#{uid}", payload.slice(:name, :url, :resource, :event, :status, :scope, :templates)
166
+ end
167
+
168
+ def delete_webhook(uid)
169
+ delete_response "/webhooks/#{uid}"
170
+ end
171
+
172
+ # Instant URLs
173
+
174
+ def list_instant_urls(params = {})
175
+ get_response "/instant_urls?#{URI.encode_www_form(params.slice(:page))}"
176
+ end
177
+
178
+ def get_instant_url(uid)
179
+ get_response "/instant_urls/#{uid}"
180
+ end
181
+
182
+ def create_instant_url(payload = {})
183
+ post_response "/instant_urls", payload.slice(:name, :template, :mode, :security, :status, :scale, :rate_limit, :template_version, :max_renders, :expires_at)
184
+ end
185
+
186
+ def update_instant_url(uid, payload = {})
187
+ patch_response "/instant_urls/#{uid}", payload.slice(:name, :template, :mode, :security, :status, :scale, :rate_limit, :template_version, :max_renders, :expires_at)
188
+ end
189
+
190
+ def delete_instant_url(uid)
191
+ delete_response "/instant_urls/#{uid}"
192
+ end
193
+
194
+ def build_instant_url(base_url, payload = {})
195
+ modifications = payload[:modifications]
196
+ mode = (payload[:mode] || "encoded").to_s
197
+
198
+ url = case mode
199
+ when "encoded"
200
+ data =
201
+ if modifications.is_a?(Hash) && modifications.keys.map(&:to_s) == ["objects"]
202
+ modifications[:objects] || modifications["objects"]
203
+ else
204
+ modifications
205
+ end
206
+ "#{base_url}?modifications=#{Base64.urlsafe_encode64(data.to_json, padding: false)}"
207
+ when "named_params"
208
+ template = modifications.is_a?(Hash) ? (modifications[:template] || modifications["template"]) : nil
209
+ objects = modifications.is_a?(Hash) ? (modifications[:objects] || modifications["objects"]) : modifications
210
+ parts = []
211
+ (template || {}).each do |k, v|
212
+ parts << "template:#{k}=#{URI.encode_www_form_component(v)}"
213
+ end
214
+ (objects || []).each do |obj|
215
+ name = obj[:name] || obj["name"]
216
+ obj.each do |k, v|
217
+ key = k.to_s
218
+ next if key == "name"
219
+ parts << "#{name}:#{key}=#{URI.encode_www_form_component(v)}"
220
+ end
221
+ end
222
+ "#{base_url}?#{parts.join('&')}"
223
+ else
224
+ raise ArgumentError, "unknown instant URL mode: #{mode.inspect}"
225
+ end
226
+
227
+ signing_key = payload[:signing_key]
228
+ return url if signing_key.nil? || signing_key.empty?
229
+ sig = OpenSSL::HMAC.hexdigest("SHA256", signing_key, url)
230
+ "#{url}&s=#{sig}"
231
+ end
232
+
233
+
234
+ private
235
+
236
+ BB_API_ENDPOINT = "https://api.bannerbear.com/v5"
237
+ BB_API_ENDPOINT_SYNCHRONOUS = "https://sync.api.bannerbear.com/v5"
238
+
239
+ def get_response(url)
240
+ response = HTTParty.get("#{BB_API_ENDPOINT}#{url}", timeout: 3, headers: { 'Authorization' => "Bearer #{@api_key}" })
241
+ body = JSON.parse(response.body)
242
+ return {"error" => body['message'], "code" => response.code} if response.code >= 400
243
+ return body
244
+ end
245
+
246
+ def patch_response(url, payload)
247
+ response = HTTParty.patch("#{BB_API_ENDPOINT}#{url}",
248
+ body: payload.to_json,
249
+ timeout: 5,
250
+ headers: {
251
+ 'Authorization' => "Bearer #{@api_key}",
252
+ 'Content-Type' => 'application/json'
253
+ }
254
+ )
255
+ body = JSON.parse(response.body)
256
+ return {"error" => body['message'], "code" => response.code} if response.code >= 400
257
+ return body
258
+ end
259
+
260
+ def post_response(url, payload, sync = false)
261
+ endpoint = BB_API_ENDPOINT
262
+ timeout = 5
263
+ if sync == true
264
+ endpoint = BB_API_ENDPOINT_SYNCHRONOUS
265
+ timeout = 10
266
+ end
267
+ response = HTTParty.post("#{endpoint}#{url}",
268
+ body: payload.to_json,
269
+ timeout: timeout,
270
+ headers: {
271
+ 'Authorization' => "Bearer #{@api_key}",
272
+ 'Content-Type' => 'application/json'
273
+ }
274
+ )
275
+ body = JSON.parse(response.body)
276
+ return {"error" => body['message'], "code" => response.code} if response.code >= 400
277
+ return body
278
+ end
279
+
280
+ def upload_response(url, data, content_type)
281
+ response = HTTParty.post("#{BB_API_ENDPOINT}#{url}",
282
+ body: data,
283
+ timeout: 30,
284
+ headers: {
285
+ 'Authorization' => "Bearer #{@api_key}",
286
+ 'Content-Type' => content_type
287
+ }
288
+ )
289
+ body = JSON.parse(response.body)
290
+ return {"error" => body['message'], "code" => response.code} if response.code >= 400
291
+ return body
292
+ end
293
+
294
+ def delete_response(url)
295
+ response = HTTParty.delete("#{BB_API_ENDPOINT}#{url}",
296
+ timeout: 5,
297
+ headers: { 'Authorization' => "Bearer #{@api_key}" }
298
+ )
299
+ body = response.body.to_s.empty? ? {} : JSON.parse(response.body)
300
+ return {"error" => body['message'], "code" => response.code} if response.code >= 400
301
+ return {"code" => response.code}
302
+ end
303
+
304
+ end
305
+
306
+ end
307
+ end
@@ -1,3 +1,3 @@
1
1
  module Bannerbear
2
- VERSION = "0.1.4"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/bannerbear.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  require "httparty"
2
2
  require "openssl"
3
3
  require "base64"
4
+ require "json"
4
5
  require "bannerbear/version"
5
6
  require "bannerbear/client"
7
+ require "bannerbear/v5/client"
6
8
 
7
9
  module Bannerbear
8
10
  class Error < StandardError; end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bannerbear
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Yongfook
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-07-20 00:00:00.000000000 Z
11
+ date: 2026-08-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty
@@ -43,6 +43,7 @@ files:
43
43
  - bin/setup
44
44
  - lib/bannerbear.rb
45
45
  - lib/bannerbear/client.rb
46
+ - lib/bannerbear/v5/client.rb
46
47
  - lib/bannerbear/version.rb
47
48
  homepage: https://github.com/yongfook/bannerbear-ruby
48
49
  licenses:
@@ -64,7 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
64
65
  - !ruby/object:Gem::Version
65
66
  version: '0'
66
67
  requirements: []
67
- rubygems_version: 3.1.6
68
+ rubygems_version: 3.5.22
68
69
  signing_key:
69
70
  specification_version: 4
70
71
  summary: Ruby wrapper for the Bannerbear API