warb 0.1.3 → 0.1.4

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: 2f9ae7b1519b693fce17eb562569b49d110f18e57faf6fe1608880af08e40bcd
4
- data.tar.gz: 931fb4cb412c47e0756eb05f21ff84b0126ef154ed9274ea83c3bd4f303f086b
3
+ metadata.gz: d0ceb84f9bce4434cb413bee092051e345693d5bfeaa4f4e529e7661eb659f50
4
+ data.tar.gz: 6ecc6f330c73559e1abd7d17cb817cb894e61fa5055cc1ec5cab9ebd35ff607e
5
5
  SHA512:
6
- metadata.gz: 7aedabac7a6c7c74932b4ff243faf3ba06b7596c3c6c77114e65db6fd74e809833d2fb76d4e592eb738d156881d2efdf016c59236607f7ab300afc592e6ee501
7
- data.tar.gz: d91b0667879f1e192d1cdc6e23048eba6326519df997546335b36df709cdddbbe8eb81605dfdd304b060e4a89da8792d0388cd62e13cf1a59773aecd3cd5c062
6
+ metadata.gz: e7be228f7823f7d9da7aa2820f4ad91f74acf13d31c8e662ab613c8748231e069527258fc7d66333007785541a419fcba26a4a2ae9aa54159b3b6891eb06d8cb
7
+ data.tar.gz: ba129a442d7afbb050591903c6b753ad4e0c62360c9c2955b640a039a79f5d8dc42a07210d1c279e651920fd49bdc00f3e0a0bd2458426f9c851015ace4b8111
@@ -11,12 +11,13 @@ It represents a generic button, which can be set with the following attributes:
11
11
  | `sub_type` | `String` | Yes | A more specific classification for the button |
12
12
 
13
13
  ## Common Button Types
14
- | Button Type | Template Instance Method | Params |
15
- |---------------|------------------------------|-------------------------|
16
- | `quick_reply` | `add_quick_reply_button` | `index` |
17
- | `voice_call` | `add_voice_call_button` | `index` |
18
- | `url` | `add_dynamic_url_button` | `index`, `text` |
19
- | `copy_code` | `add_copy_code_button` | `index`, `coupon_code` |
14
+ | Button Type | Template Instance Method | Params |
15
+ |---------------|------------------------------|------------------------------------------|
16
+ | `quick_reply` | `add_quick_reply_button` | `index` |
17
+ | `voice_call` | `add_voice_call_button` | `index` |
18
+ | `url` | `add_dynamic_url_button` | `index`, `text` |
19
+ | `copy_code` | `add_copy_code_button` | `index`, `coupon_code` |
20
+ | `flow` | `add_flow_button` | `index`, `flow_token`, `flow_action_data`|
20
21
 
21
22
  Please, refer to our [templates messaging documentation](../messages/template.md) for more info. You can check the methods to insert a button in the "Adding Buttons" section.
22
23
 
@@ -0,0 +1,102 @@
1
+ # FlowButton
2
+
3
+ ```Warb::Components::FlowButton``` is a component used in template messages for flow buttons.
4
+ This button type allows you to link a template directly with a WhatsApp Flow experience.
5
+
6
+ ## Attributes
7
+ | Attribute | Type | Required | Description |
8
+ | ------------------ | --------- | -------- |-----------------------------------------------------------------|
9
+ | `index` | `Integer` | Yes | An identifier or position for the button in the template. |
10
+ | `sub_type` | `String` | Yes | Always `"flow"` for this button type. |
11
+ | `flow_token` | `String` | No | If not set, the API defaults to `"unused"`. |
12
+ | `flow_action_data` | `Hash` | No | A key-value payload passed to the flow as pre-filled form data. |
13
+
14
+
15
+
16
+ ## Examples
17
+ ###### Basic Flow button with token
18
+ ```ruby
19
+ flow_button = Warb::Components::FlowButton.new(
20
+ index: 0,
21
+ sub_type: "flow",
22
+ flow_token: "TOKEN_123"
23
+ )
24
+
25
+ flow_button.to_h
26
+ => {
27
+ type: "button",
28
+ sub_type: "flow",
29
+ index: 0,
30
+ parameters: [
31
+ {
32
+ type: "action",
33
+ action: {
34
+ flow_token: "TOKEN_123"
35
+ }
36
+ }
37
+ ]
38
+ }
39
+ ```
40
+
41
+ ##### Flow button with token and action data
42
+ ```ruby
43
+ flow_button = Warb::Components::FlowButton.new(
44
+ index: 1,
45
+ sub_type: "flow",
46
+ flow_token: "TOKEN_ABC",
47
+ flow_action_data: { name: "John", cpf: "11122233344" }
48
+ )
49
+
50
+ flow_button.to_h
51
+ => {
52
+ type: "button",
53
+ sub_type: "flow",
54
+ index: 1,
55
+ parameters: [
56
+ {
57
+ type: "action",
58
+ action: {
59
+ flow_token: "TOKEN_ABC",
60
+ flow_action_data: { name: "John", cpf: "11122233344" }
61
+ }
62
+ }
63
+ ]
64
+ }
65
+ ```
66
+
67
+ ##### Flow button without optional fields
68
+ ```ruby
69
+ flow_button = Warb::Components::FlowButton.new(index: 2, sub_type: "flow")
70
+ flow_button.to_h
71
+ => {
72
+ type: "button",
73
+ sub_type: "flow",
74
+ index: 2,
75
+ parameters: [
76
+ {
77
+ type: "action",
78
+ action: {}
79
+ }
80
+ ]
81
+ }
82
+ ```
83
+ ### Usage in Templates
84
+
85
+ Flow buttons are typically added to templates using the add_flow_button method:
86
+
87
+ ```ruby
88
+ template = Warb::Resources::Template.new(name: "my_template", language: "en_US")
89
+
90
+ # Add a flow button with token
91
+ template.add_flow_button(index: 0, flow_token: "TOKEN_123")
92
+
93
+ # Add a flow button with token and action data
94
+ template.add_flow_button(index: 1, flow_token: "TOKEN_ABC", flow_action_data: { name: "Jane", dob: "1990-01-01" })
95
+
96
+ # Or using a block for more complex configuration
97
+ template.add_flow_button do |button|
98
+ button.index = 0
99
+ button.flow_token = "TOKEN_DYNAMIC"
100
+ button.flow_action_data = { email: "user@example.com" }
101
+ end
102
+ ```
@@ -1,12 +1,248 @@
1
1
  # Flow
2
2
 
3
- Flow is a special type of message. We can summarize it simply as a form.
3
+ Flow is a special type of interactive WhatsApp message that provides a "form-like" experience.
4
+ With ```Warb.flow``` you can send static (navigate) or dynamic (data exchange) flows, both in draft and published modes.
4
5
 
5
- At this point of writing, this gem only supports sending existing flows, and it can only send flows which are in draft.
6
+ Prerequisites (Meta)
7
+ Business Account + Developer Account
8
+ Verified Business to send flows in production WhatsApp (unverified accounts can only test in Meta’s Flow editor)
9
+ For dynamic flows: endpoint + encryption configured in Meta (to handle user responses)
6
10
 
7
- To send flows, you can do as following:
11
+ ### Quick Examples
12
+
13
+ ##### Send a static draft flow:
14
+ ```ruby
15
+ Warb.flow.dispatch(recipient_number, flow_id: "0000000000000000", mode: "draft", screen: "INITIAL", body: "Open flow")
16
+ ```
17
+
18
+ ##### Send a dynamic draft flow:
19
+ ```ruby
20
+ Warb.flow.dispatch(recipient_number,
21
+ flow_id: "0000000000000000",
22
+ mode: "draft",
23
+ flow_action: "data_exchange",
24
+ body: "Open flow"
25
+ )
26
+ ```
27
+
28
+ ##### Send a dynamic published flow with custom header, body, and footer:
29
+ ```ruby
30
+ Warb.flow.dispatch(recipient_number) do |flow|
31
+ flow.flow_id = "0000000000000000"
32
+ flow.flow_action = "data_exchange"
33
+ flow.mode = "published"
34
+ flow.body = "Hello! Please follow the instructions."
35
+ flow.footer = "Need help? Reply to this message."
36
+
37
+ flow.header = {
38
+ type: "document",
39
+ document: {
40
+ link: "https://example.com/contract.pdf",
41
+ filename: "contract.pdf"
42
+ }
43
+ }
44
+
45
+ flow.flow_cta = "Sign"
46
+ end
47
+ ```
48
+
49
+ ### Dispatching Flow Messages
8
50
  ```ruby
9
- Warb.flow.dispatch(recipient_number, flow_id: "flow_id", screen: "screen")
51
+ Warb.flow.dispatch(recipient_number, **params, &block)
10
52
  ```
53
+ ##### Parameters
54
+ | Attribute | Type | Required | Description |
55
+ | ------------- | -------- | ---------------------------------- | ----------------------------------------------------------- |
56
+ | `flow_id` | `String` | Yes | The ID of the Flow created in Meta. |
57
+ | `body` | `String` | Yes | Body text shown in the Flow message. |
58
+ | `flow_action` | `String` | No | `"navigate"` (default) or `"data_exchange"`. |
59
+ | `mode` | `String` | Yes if `flow mode == 'draft'` | `"published"` (default) or `"draft"`. |
60
+ | `screen` | `String` | Yes if `flow_action == "navigate"` | Initial screen to navigate to. |
61
+ | `flow_cta` | `String` | No | Label for the button that opens the Flow. |
62
+ | `flow_token` | `String` | No | Token for dynamic flows (encryption/validation). |
63
+ | `data` | `Hash` | No | Prefill data for navigate flows. |
64
+ | `header` | `Hash` | No | Optional header (see below). |
65
+ | `footer` | `String` | No | Optional footer text. |
66
+
67
+ ### Supported Headers
68
+
69
+ You must provide exactly one of the following header types:
11
70
 
12
- `flow_id` must be the ID of the flow and `screen` must be the ID of the first screen of the flow.
71
+ ##### Text
72
+ ```ruby
73
+ { type: "text", text: "Flow title" }
74
+ ```
75
+
76
+ ##### Image
77
+ ```ruby
78
+ { type: "image", image: { id: "MEDIA_ID" } }
79
+ # or
80
+ { type: "image", image: { link: "https://example.com/img.jpg" } }
81
+ ```
82
+
83
+ ##### Video
84
+ ```ruby
85
+ { type: "video", video: { id: "MEDIA_ID" } }
86
+ # or
87
+ { type: "video", video: { link: "https://example.com/video.mp4" } }
88
+ ```
89
+
90
+ ##### Document
91
+ ```ruby
92
+ { type: "document", document: { id: "MEDIA_ID" } }
93
+ # or
94
+ { type: "document", document: { link: "https://example.com/file.pdf", filename: "file.pdf" } }
95
+ ```
96
+
97
+ ##### Note:
98
+
99
+ id must be obtained from a media upload (Warb.image/video/document.upload).
100
+
101
+ link must be a public URL. For documents, filename is required to determine preview capabilities.
102
+
103
+ ### Modes and Actions
104
+ ##### mode
105
+
106
+ "published" (default): Published flow. Allows customizing header, body, footer, and flow_cta.
107
+
108
+ "draft": Draft flow. Usable via Meta Flow editor and sometimes on WhatsApp, but some elements (like body text and CTA) may be restricted.
109
+
110
+ ##### flow_action
111
+
112
+ "navigate" (default) → static flow
113
+ Requires screen, can include initial data:
114
+ ```ruby
115
+ Warb.flow.dispatch(recipient_number,
116
+ flow_id: "0000000000000000",
117
+ screen: "INITIAL",
118
+ body: "Continue",
119
+ data: { prefill: { name: "Alice", email: "alice@example.com" } }
120
+ )
121
+ ```
122
+
123
+ "data_exchange" → dynamic flow
124
+ No flow_action_payload (omitted automatically):
125
+ ```ruby
126
+ Warb.flow.dispatch(recipient_number,
127
+ flow_id: "0000000000000000",
128
+ flow_action: "data_exchange",
129
+ body: "Fill the form",
130
+ )
131
+ ```
132
+
133
+ ### Block Building
134
+
135
+ You can also build flows using a block:
136
+ ```ruby
137
+ Warb.flow.dispatch(recipient_number) do |flow|
138
+ flow.flow_id = "0000000000000000"
139
+ flow.flow_action = "data_exchange"
140
+ flow.mode = "published"
141
+ flow.body = "We need some information"
142
+
143
+ flow.header = { type: "text", text: "Registration" }
144
+ flow.footer = "Thanks!"
145
+
146
+ flow.flow_cta = "Open"
147
+ end
148
+ ```
149
+
150
+ ### Validations
151
+
152
+ Before sending, the gem enforces:
153
+
154
+ flow_id is required → raises ArgumentError: flow_id is required
155
+
156
+ body is required → raises ArgumentError: body is required for flow message
157
+
158
+ If flow_action == "navigate" then screen is required →
159
+ raises ArgumentError: screen is required for flow_action=navigate
160
+
161
+ The Meta API may return additional errors (invalid parameters, missing fields, etc.), which are raised as Warb::BadRequest, Warb::RequestError, etc.
162
+
163
+ Example Scenarios
164
+ 1) Static / Draft
165
+ ```ruby
166
+ Warb.flow.dispatch(recipient_number,
167
+ flow_id: "0000000000000000",
168
+ screen: "INITIAL",
169
+ mode: "draft",
170
+ body: "Open flow"
171
+ )
172
+ ```
173
+
174
+ 2) Dynamic / Draft
175
+ ```ruby
176
+ Warb.flow.dispatch(recipient_number,
177
+ flow_id: "0000000000000000",
178
+ flow_action: "data_exchange",
179
+ mode: "draft",
180
+ body: "Fill the form"
181
+ )
182
+ ```
183
+
184
+ 3) Dynamic / Published with Document Header
185
+ ```ruby
186
+ Warb.flow.dispatch(recipient_number) do |flow|
187
+ flow.flow_id = "0000000000000000"
188
+ flow.flow_action = "data_exchange"
189
+ flow.mode = "published"
190
+ flow.body = "Please review and sign the document."
191
+ flow.footer = "Reply if you need assistance."
192
+ flow.flow_cta = "Sign"
193
+
194
+ flow.header = {
195
+ type: "document",
196
+ document: {
197
+ link: "https://www.thecampusqdl.com/uploads/files/pdf_sample_2.pdf",
198
+ filename: "pdf_sample_2.pdf"
199
+ }
200
+ }
201
+ end
202
+ ```
203
+
204
+ 4) Static / Published with Image Header (id) and Prefill Data
205
+ ```ruby
206
+ image_id = Warb.image.upload(file_path: "banner.jpg", file_type: "image/jpeg")
207
+
208
+ Warb.flow.dispatch(recipient_number) do |flow|
209
+ flow.flow_id = "0000000000000000"
210
+ flow.mode = "published"
211
+ flow.body = "Open and check your data"
212
+
213
+ flow.header = { type: "image", image: { id: image_id } }
214
+
215
+ flow.screen = "INITIAL"
216
+ flow.data = { prefill: { name: "John", email: "john@example.com" } }
217
+ end
218
+ ```
219
+
220
+ ### Generated Payload (Reference)
221
+
222
+ Example payload produced by the gem:
223
+ ```ruby
224
+ {
225
+ "type": "interactive",
226
+ "interactive": {
227
+ "type": "flow",
228
+ "header": { ... },
229
+ "body": { "text": "..." },
230
+ "footer": { "text": "..." },
231
+ "action": {
232
+ "name": "flow",
233
+ "parameters": {
234
+ "flow_message_version": "3",
235
+ "flow_id": "....",
236
+ "flow_action": "navigate" | "data_exchange",
237
+ "mode": "published" | "draft",
238
+ "flow_cta": "Open",
239
+ "flow_token": "TOKEN",
240
+ "flow_action_payload": {
241
+ "screen": "INITIAL",
242
+ "data": { "prefill": { ... } }
243
+ }
244
+ }
245
+ }
246
+ }
247
+ }
248
+ ```
@@ -271,6 +271,7 @@ If your template supports buttons, you can add them using the following methods:
271
271
  | `url` | `add_auth_code_button` | `index`, `text` |
272
272
  | `copy_code` | `add_copy_code_button` | `index`, `coupon_code` |
273
273
  | `voice_call` | `add_voice_call_button` | `index` |
274
+ | `flow` | `add_flow_button` | `index`, `flow_token`, `flow_action_data` |
274
275
  | `doesn't apply` | `add_button` | `instance`, `&block` |
275
276
 
276
277
  You can either use the keyword parameters or set the attributes using a block:
@@ -300,6 +301,12 @@ Warb.template.dispatch(recipient_number) do |template|
300
301
 
301
302
  # Add a voice call button
302
303
  template.add_voice_call_button
304
+
305
+ # Add a flow button
306
+ template.add_flow_button do |button|
307
+ button.flow_token = 'FLOWTOKEN'
308
+ button.flow_action_data = { name: 'John' }
309
+ end
303
310
  end
304
311
  ```
305
312
 
@@ -325,3 +332,42 @@ indices match the button positions defined in your template. The `index` is auto
325
332
  don't do it manually, but it is done based on the number of buttons added with the methods above,
326
333
  so if your template has a button that doesn't need configuration like the static url button you'll
327
334
  have provide the position of the other buttons.
335
+
336
+
337
+ #### Creating Templates
338
+
339
+ To create a template, you need to provide the template name, language, category and the body (components).
340
+
341
+ ```ruby
342
+ Warb.template.create(
343
+ name: "my_template_001",
344
+ language: Warb::Language::ENGLISH_US,
345
+ category: Warb::Category::MARKETING,
346
+ body: Warb::Resources::Text.new(
347
+ text: "Hello {{1}}, welcome to our service!",
348
+ examples: ["John"]
349
+ )
350
+ )
351
+ ```
352
+
353
+ **Required Parameters:**
354
+ | Parameter | Type | Description |
355
+ |------------|----------|----------------------------------------------|
356
+ | `name` | `String` | Unique template name (snake_case) |
357
+ | `language` | `String` | The language to use for the template |
358
+ | `category` | `String` | Template category (MARKETING, UTILITY, etc.) |
359
+
360
+ **Template Categories:**
361
+ - `Warb::Category::MARKETING` - Promotional content
362
+ - `Warb::Category::UTILITY` - Transactional messages
363
+ - `Warb::Category::AUTHENTICATION` - Security codes
364
+
365
+ **Note**: Currently, the creation only supports body text, more resource types are coming soon.
366
+
367
+ #### Deleting Templates
368
+
369
+ To delete a template, you need to provide the template name.
370
+
371
+ ```ruby
372
+ Warb.template.delete("my_template_001")
373
+ ```
@@ -1,6 +1,6 @@
1
1
  # Resources
2
2
 
3
- Most of the resources can be sent as message. Check the [messages](../messages/index.md) for detailed info.
3
+ Most of the resources can be sent as message. Check the [messages](../messages/README.md) for detailed info.
4
4
 
5
5
  But there are some resources which act more like a component, not being used alone by themself.
6
6
 
@@ -11,4 +11,4 @@ Such resources, and where they are used, are listed bellow:
11
11
  |-----------------------------|------------------------------|--------------------------------------------------------------------------------------|
12
12
  | `Warb::Resources::Currency` | [Currency](./currency.md) | [Template Messaging](../messages/template.md) |
13
13
  | `Warb::Resources::DateTime` | [Date Time](./date_time.md) | [Template Messaging](../messages/template.md) |
14
- | `Warb::Resources::Text` | [Text](./text.md) | [Text Messaging](../messages/text.md), [Template Messaging](../messages/template.md) |
14
+ | `Warb::Resources::Text` | [Text](./text.md) | [Text Messaging](../messages/text.md), [Template Messaging](../messages/template.md) |
data/docs/setup.md CHANGED
@@ -43,4 +43,48 @@ Warb.setup do |config|
43
43
  end
44
44
  ```
45
45
 
46
- Also, note that calling `Warb.setup` multiple times **WILL NOT** override the previous configuration, so you can use it to change the global configuration at any time.
46
+ Also, note that calling `Warb.setup` multiple times **WILL NOT** override the previous configuration, so you can use it to change the global configuration at any time.
47
+
48
+ ## Phone numbers (quality monitoring)
49
+
50
+ You can fetch all phone numbers attached to your WhatsApp Business Account (WABA) and their quality/operational signals.
51
+
52
+ Requirements: make sure you have configured a business_id in your global setup (this method uses the business context, not the sender/phone context).
53
+ ```ruby
54
+ Warb.setup do |config|
55
+ config.access_token = "ACCESS_TOKEN"
56
+ config.business_id = "BUSINESS_ID" # <-- required here
57
+ config.sender_id = "SENDER_ID" # still used for message dispatch
58
+ end
59
+ ```
60
+
61
+ #### Global usage
62
+ ```ruby
63
+ phones = Warb.list_phone_numbers
64
+ ```
65
+
66
+ #### Sample response (unwrapped data array):
67
+ ```ruby
68
+ => [
69
+ {
70
+ "verified_name" => "Test",
71
+ "code_verification_status" => "NOT_VERIFIED",
72
+ "display_phone_number" => "00000000000",
73
+ "quality_rating" => "GREEN",
74
+ "platform_type" => "CLOUD_API",
75
+ "throughput" => { "level" => "STANDARD" },
76
+ "webhook_configuration" => { "application" => "https://example.com/" },
77
+ "id" => "(phone_number_id)"
78
+ }
79
+ ]
80
+ ```
81
+
82
+ #### Notes
83
+
84
+ This method returns the data array directly. If you need paging cursors, call the raw client:
85
+ ```ruby
86
+ raw = Warb.client.get("phone_numbers", {}, endpoint_prefix: :business_id)
87
+ raw.body # => { "data" => [...], "paging" => { "cursors" => {...} } }
88
+ ```
89
+
90
+ Non-2xx responses raise Warb::RequestError (or subclasses), so you can rescue them in your app.
data/examples/webhook.rb CHANGED
@@ -1,17 +1,49 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../lib/warb'
3
4
  require 'sinatra/base'
4
5
  require 'faraday'
6
+ require 'openssl'
5
7
 
6
8
  class Webhook < Sinatra::Base
9
+
10
+ Warb.setup do |config|
11
+ config.access_token = token
12
+ config.business_id = business
13
+ config.sender_id = sender
14
+ end
15
+
7
16
  configure do
8
17
  set :bind, '0.0.0.0'
9
18
  set :port, 3000
10
19
  set :host_authorization, { permitted_hosts: [] }
11
20
  end
12
21
 
22
+ helpers do
23
+ def verify_signature!(raw_body)
24
+ header = request.env['HTTP_X_HUB_SIGNATURE_256']
25
+
26
+ halt 400, 'Missing X-Hub-Signature-256' if APP_SECRET && (!header || header.empty?)
27
+
28
+ received = header.sub('sha256=', '')
29
+ expected = OpenSSL::HMAC.hexdigest('SHA256', APP_SECRET, raw_body)
30
+
31
+ unless Rack::Utils.secure_compare(received, expected)
32
+ puts "⚠️ Invalid webhook signature."
33
+ halt 403, 'Invalid signature'
34
+ end
35
+
36
+ true
37
+ end
38
+ end
39
+
13
40
  post '/webhook' do
14
- request_body = JSON.parse(request.body.read)
41
+ request.body.rewind
42
+ raw_body = request.body.read
43
+
44
+ verify_signature!(raw_body)
45
+
46
+ request_body = JSON.parse(raw_body)
15
47
 
16
48
  puts "\n🪝 Incoming webhook message: #{request_body}"
17
49
 
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Category
5
+ MARKETING = 'MARKETING'
6
+ UTILITY = 'UTILITY'
7
+ end
8
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Components
5
+ class FlowButton < Button
6
+ BUTTON_TYPE = 'flow'
7
+
8
+ attr_accessor :flow_token, :flow_action_data
9
+
10
+ def to_h
11
+ button_payload = super
12
+
13
+ token = flow_token || @params[:flow_token]
14
+ data = flow_action_data || @params[:flow_action_data]
15
+
16
+ action = {}
17
+ action[:flow_token] = token if token
18
+ action[:flow_action_data] = data if data
19
+
20
+ button_payload[:parameters] = [{ type: 'action', action: action }]
21
+
22
+ button_payload
23
+ end
24
+
25
+ private
26
+
27
+ def button_type
28
+ BUTTON_TYPE
29
+ end
30
+ end
31
+ end
32
+ end
@@ -3,34 +3,94 @@
3
3
  module Warb
4
4
  module Resources
5
5
  class Flow < Resource
6
- attr_accessor :flow_id, :screen
6
+ include Helpers::Header
7
+
8
+ attr_accessor :flow_id, :screen, :flow_action, :mode,
9
+ :flow_cta, :flow_token, :body, :header, :footer, :data,
10
+ :draft, :data_exchange
7
11
 
8
- # rubocop:disable Metrics/MethodLength
9
12
  def build_payload
13
+ validate!
14
+
10
15
  {
11
16
  type: 'interactive',
12
- interactive: {
13
- type: 'flow',
14
- body: {
15
- text: 'Not shown in draft mode'
16
- },
17
- action: {
18
- name: 'flow',
19
- parameters: {
20
- flow_message_version: '3',
21
- flow_action: 'navigate',
22
- flow_id: flow_id || @params[:flow_id],
23
- flow_cta: 'Not shown in draft mode',
24
- mode: 'draft',
25
- flow_action_payload: {
26
- screen: screen || @params[:screen]
27
- }
28
- }
29
- }
17
+ interactive: build_interactive
18
+ }
19
+ end
20
+
21
+ private
22
+
23
+ def build_interactive
24
+ interactive = {
25
+ type: 'flow',
26
+ action: {
27
+ name: 'flow',
28
+ parameters: build_action_parameters
30
29
  }
31
30
  }
31
+
32
+ header = resolve(:header)
33
+ if header.is_a?(Hash)
34
+ interactive[:header] = header
35
+ elsif header.respond_to?(:build_header)
36
+ interactive[:header] = header.build_header
37
+ end
38
+
39
+ resolve(:body)
40
+ .then { |body| interactive[:body] = { text: body } }
41
+
42
+ resolve(:footer)
43
+ .then { |footer| interactive[:footer] = { text: footer } unless blank?(footer) }
44
+
45
+ interactive
46
+ end
47
+
48
+ def build_action_parameters
49
+ params = {
50
+ flow_message_version: '3',
51
+ flow_id: resolve(:flow_id),
52
+ flow_action: final_action,
53
+ mode: final_mode
54
+ }
55
+
56
+ resolve(:flow_cta)
57
+ .then { |label| params[:flow_cta] = label unless blank?(label) }
58
+
59
+ resolve(:flow_token)
60
+ .then { |token| params[:flow_token] = token unless blank?(token) }
61
+
62
+ if final_action == 'navigate'
63
+ payload = { screen: resolve(:screen) }
64
+ initial = resolve(:data)
65
+ payload[:data] = initial unless blank?(initial)
66
+ params[:flow_action_payload] = payload
67
+ end
68
+
69
+ params
70
+ end
71
+
72
+ def final_action
73
+ explicit = raw_value(:flow_action)
74
+ return explicit.to_s unless blank?(explicit)
75
+
76
+ resolve(:data_exchange) ? 'data_exchange' : 'navigate'
77
+ end
78
+
79
+ def final_mode
80
+ explicit = raw_value(:mode)
81
+ return explicit.to_s unless blank?(explicit)
82
+
83
+ resolve(:draft) ? 'draft' : 'published'
84
+ end
85
+
86
+ def validate!
87
+ validates :flow_id, required: true
88
+ validates :body, required: true
89
+
90
+ validates :screen,
91
+ required: -> { final_action == 'navigate' },
92
+ message: 'screen is required for flow_action=navigate'
32
93
  end
33
- # rubocop:enable Metrics/MethodLength
34
94
  end
35
95
  end
36
96
  end
@@ -0,0 +1,35 @@
1
+ module Warb
2
+ module Resources
3
+ module Helpers
4
+ module Header
5
+ def add_text_header(content: nil, message: nil, text: nil, parameter_name: nil, &block)
6
+ add_header(Warb::Resources::Text.new(content:, message:, text:, parameter_name:), &block)
7
+ end
8
+
9
+ def add_image_header(media_id: nil, link: nil, &block)
10
+ add_header(Warb::Resources::Image.new(media_id:, link:), &block)
11
+ end
12
+
13
+ def add_document_header(media_id: nil, link: nil, filename: nil, &block)
14
+ add_header(Warb::Resources::Document.new(media_id:, link:, filename:), &block)
15
+ end
16
+
17
+ def add_video_header(media_id: nil, link: nil, &block)
18
+ add_header(Warb::Resources::Video.new(media_id:, link:), &block)
19
+ end
20
+
21
+ def add_location_header(latitude: nil, longitude: nil, address: nil, name: nil, &block)
22
+ add_header(Warb::Resources::Location.new(latitude:, longitude:, address:, name:), &block)
23
+ end
24
+
25
+ private
26
+
27
+ def add_header(instance, &)
28
+ @header = instance
29
+
30
+ block_given? ? @header.tap(&) : @header
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -3,6 +3,8 @@
3
3
  module Warb
4
4
  module Resources
5
5
  class Resource
6
+ include Validation
7
+
6
8
  def initialize(**params)
7
9
  @params = params
8
10
  end
@@ -3,7 +3,9 @@
3
3
  module Warb
4
4
  module Resources
5
5
  class Template < Resource
6
- attr_accessor :name, :language, :resources, :header, :buttons
6
+ include Helpers::Header
7
+
8
+ attr_accessor :name, :language, :resources, :header, :category, :body, :buttons
7
9
 
8
10
  def initialize(**params)
9
11
  super
@@ -11,6 +13,8 @@ module Warb
11
13
  @name = params[:name]
12
14
  @language = params[:language]
13
15
  @resources = params[:resources]
16
+ @category = params[:category]
17
+ @body = params[:body]
14
18
  @buttons = []
15
19
  end
16
20
 
@@ -33,6 +37,17 @@ module Warb
33
37
  end
34
38
  # rubocop:enable Metrics/MethodLength
35
39
 
40
+ def creation_payload
41
+ {
42
+ name: name,
43
+ language: language,
44
+ category: category,
45
+ components: [
46
+ body&.build_template_example_parameter
47
+ ].compact
48
+ }
49
+ end
50
+
36
51
  def add_currency_parameter(parameter_name = nil, **params, &)
37
52
  add_parameter(parameter_name, Currency.new(**params), &)
38
53
  end
@@ -45,26 +60,6 @@ module Warb
45
60
  add_parameter(parameter_name, Text.new(**params), &)
46
61
  end
47
62
 
48
- def add_text_header(content: nil, message: nil, text: nil, parameter_name: nil, &block)
49
- add_header(Text.new(content:, message:, text:, parameter_name:), &block)
50
- end
51
-
52
- def add_image_header(media_id: nil, link: nil, &block)
53
- add_header(Image.new(media_id:, link:), &block)
54
- end
55
-
56
- def add_document_header(media_id: nil, link: nil, filename: nil, &block)
57
- add_header(Document.new(media_id:, link:, filename:), &block)
58
- end
59
-
60
- def add_video_header(media_id: nil, link: nil, &block)
61
- add_header(Video.new(media_id:, link:), &block)
62
- end
63
-
64
- def add_location_header(latitude: nil, longitude: nil, address: nil, name: nil, &block)
65
- add_header(Location.new(latitude:, longitude:, address:, name:), &block)
66
- end
67
-
68
63
  def add_quick_reply_button(index: position, &block)
69
64
  add_button(Warb::Components::QuickReplyButton.new(index:), &block)
70
65
  end
@@ -83,6 +78,13 @@ module Warb
83
78
  add_button(Warb::Components::VoiceCallButton.new(index:), &block)
84
79
  end
85
80
 
81
+ def add_flow_button(index: position, flow_token: nil, flow_action_data: nil, &block)
82
+ add_button(
83
+ Warb::Components::FlowButton.new(index: index, flow_token: flow_token,
84
+ flow_action_data: flow_action_data), &block
85
+ )
86
+ end
87
+
86
88
  def add_button(instance, &)
87
89
  return @buttons << instance.to_h unless block_given?
88
90
 
@@ -91,12 +93,6 @@ module Warb
91
93
 
92
94
  private
93
95
 
94
- def add_header(instance, &)
95
- @header = instance
96
-
97
- block_given? ? @header.tap(&) : @header
98
- end
99
-
100
96
  def component_header
101
97
  return unless header.is_a? Resource
102
98
 
@@ -3,7 +3,7 @@
3
3
  module Warb
4
4
  module Resources
5
5
  class Text < Resource
6
- attr_accessor :content, :text, :message, :preview_url, :parameter_name
6
+ attr_accessor :content, :text, :message, :preview_url, :parameter_name, :examples
7
7
 
8
8
  def build_header
9
9
  { type: 'text', text: message_per_priority }.tap do |header|
@@ -37,6 +37,16 @@ module Warb
37
37
  }
38
38
  end
39
39
 
40
+ def build_template_example_parameter
41
+ { type: 'body', text: message_per_priority }.tap do |param|
42
+ examples ||= @params[:examples]
43
+
44
+ next unless examples.is_a?(Array)
45
+
46
+ param[:example] = { body_text: [examples] }
47
+ end
48
+ end
49
+
40
50
  private
41
51
 
42
52
  def message_per_priority
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Resources
5
+ module Validation
6
+ def blank?(val)
7
+ val.respond_to?(:empty?) ? val.empty? : !val
8
+ end
9
+
10
+ def raw_value(field)
11
+ respond_to?(field) ? public_send(field) : nil
12
+ end
13
+
14
+ def resolve(field, default = nil)
15
+ val = raw_value(field)
16
+ val = @params[field] if blank?(val) && defined?(@params) && @params&.key?(field)
17
+ val = default if blank?(val) && !default.nil?
18
+ val
19
+ end
20
+
21
+ def validates(field, required: false, message: nil)
22
+ needed = required.respond_to?(:call) ? required.call : required
23
+ return unless needed
24
+ return unless blank?(resolve(field))
25
+
26
+ raise ArgumentError, (message || "#{field} is required")
27
+ end
28
+ end
29
+ end
30
+ end
@@ -2,6 +2,15 @@
2
2
 
3
3
  module Warb
4
4
  class TemplateDispatcher < Dispatcher
5
+ def create(**args)
6
+ template = Resources::Template.new(**args)
7
+ @client.post('message_templates', template.creation_payload, endpoint_prefix: :business_id)
8
+ end
9
+
10
+ def delete(template_name)
11
+ @client.delete('message_templates', { name: template_name }, endpoint_prefix: :business_id).body
12
+ end
13
+
5
14
  def list(**args)
6
15
  filter = args.slice(:limit, :fields, :after, :before)
7
16
  filter[:fields] = filter[:fields].join(',') if filter[:fields].is_a?(Array)
data/lib/warb/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Warb
4
- VERSION = '0.1.3'
4
+ VERSION = '0.1.4'
5
5
  end
data/lib/warb.rb CHANGED
@@ -2,14 +2,22 @@
2
2
 
3
3
  require 'faraday'
4
4
  require 'faraday/multipart'
5
+
5
6
  require_relative 'warb/version'
6
7
  require_relative 'warb/language'
8
+ require_relative 'warb/category'
7
9
  require_relative 'warb/configuration'
8
10
  require_relative 'warb/dispatcher_concern'
9
11
  require_relative 'warb/client'
12
+
13
+ # Error/response stack
10
14
  require_relative 'warb/errors'
11
15
  require_relative 'warb/response_error_handler'
12
16
  require_relative 'warb/response'
17
+
18
+ # Resources
19
+ require_relative 'warb/resources/validation'
20
+ require_relative 'warb/resources/helpers/header'
13
21
  require_relative 'warb/resources/resource'
14
22
  require_relative 'warb/resources/text'
15
23
  require_relative 'warb/resources/image'
@@ -28,13 +36,18 @@ require_relative 'warb/resources/template'
28
36
  require_relative 'warb/resources/currency'
29
37
  require_relative 'warb/resources/date_time'
30
38
  require_relative 'warb/resources/flow'
39
+
40
+ # Dispatchers
31
41
  require_relative 'warb/dispatcher'
32
42
  require_relative 'warb/media_dispatcher'
33
43
  require_relative 'warb/template_dispatcher'
34
44
  require_relative 'warb/indicator_dispatcher'
45
+
46
+ # Utils and components
35
47
  require_relative 'warb/utils'
36
48
  require_relative 'warb/components/component'
37
49
  require_relative 'warb/components/button'
50
+ require_relative 'warb/components/flow_button'
38
51
  require_relative 'warb/components/quick_reply_button'
39
52
  require_relative 'warb/components/url_button'
40
53
  require_relative 'warb/components/copy_code_button'
@@ -73,5 +86,9 @@ module Warb
73
86
 
74
87
  client
75
88
  end
89
+
90
+ def list_phone_numbers
91
+ client.get('phone_numbers', endpoint_prefix: :business_id).body['data']
92
+ end
76
93
  end
77
94
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: warb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rebase
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-08-22 00:00:00.000000000 Z
11
+ date: 2025-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -57,6 +57,7 @@ files:
57
57
  - docs/components/copy_code_button.md
58
58
  - docs/components/cta_action.md
59
59
  - docs/components/email.md
60
+ - docs/components/flow_button.md
60
61
  - docs/components/list_action.md
61
62
  - docs/components/name.md
62
63
  - docs/components/org.md
@@ -85,9 +86,9 @@ files:
85
86
  - docs/messages/template.md
86
87
  - docs/messages/text.md
87
88
  - docs/messages/video.md
89
+ - docs/resources/README.md
88
90
  - docs/resources/currency.md
89
91
  - docs/resources/date_time.md
90
- - docs/resources/index.md
91
92
  - docs/resources/text.md
92
93
  - docs/setup.md
93
94
  - docs/webhook.md
@@ -104,6 +105,7 @@ files:
104
105
  - examples/video.rb
105
106
  - examples/webhook.rb
106
107
  - lib/warb.rb
108
+ - lib/warb/category.rb
107
109
  - lib/warb/client.rb
108
110
  - lib/warb/components/action.rb
109
111
  - lib/warb/components/address.rb
@@ -111,6 +113,7 @@ files:
111
113
  - lib/warb/components/component.rb
112
114
  - lib/warb/components/copy_code_button.rb
113
115
  - lib/warb/components/email.rb
116
+ - lib/warb/components/flow_button.rb
114
117
  - lib/warb/components/name.rb
115
118
  - lib/warb/components/org.rb
116
119
  - lib/warb/components/phone.rb
@@ -132,6 +135,7 @@ files:
132
135
  - lib/warb/resources/date_time.rb
133
136
  - lib/warb/resources/document.rb
134
137
  - lib/warb/resources/flow.rb
138
+ - lib/warb/resources/helpers/header.rb
135
139
  - lib/warb/resources/image.rb
136
140
  - lib/warb/resources/interactive_call_to_action_url.rb
137
141
  - lib/warb/resources/interactive_list.rb
@@ -143,6 +147,7 @@ files:
143
147
  - lib/warb/resources/sticker.rb
144
148
  - lib/warb/resources/template.rb
145
149
  - lib/warb/resources/text.rb
150
+ - lib/warb/resources/validation.rb
146
151
  - lib/warb/resources/video.rb
147
152
  - lib/warb/response.rb
148
153
  - lib/warb/response_error_handler.rb