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 +4 -4
- data/docs/components/button.md +7 -6
- data/docs/components/flow_button.md +102 -0
- data/docs/messages/flow.md +241 -5
- data/docs/messages/template.md +46 -0
- data/docs/resources/{index.md → README.md} +2 -2
- data/docs/setup.md +45 -1
- data/examples/webhook.rb +33 -1
- data/lib/warb/category.rb +8 -0
- data/lib/warb/components/flow_button.rb +32 -0
- data/lib/warb/resources/flow.rb +81 -21
- data/lib/warb/resources/helpers/header.rb +35 -0
- data/lib/warb/resources/resource.rb +2 -0
- data/lib/warb/resources/template.rb +23 -27
- data/lib/warb/resources/text.rb +11 -1
- data/lib/warb/resources/validation.rb +30 -0
- data/lib/warb/template_dispatcher.rb +9 -0
- data/lib/warb/version.rb +1 -1
- data/lib/warb.rb +17 -0
- metadata +8 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d0ceb84f9bce4434cb413bee092051e345693d5bfeaa4f4e529e7661eb659f50
|
|
4
|
+
data.tar.gz: 6ecc6f330c73559e1abd7d17cb817cb894e61fa5055cc1ec5cab9ebd35ff607e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e7be228f7823f7d9da7aa2820f4ad91f74acf13d31c8e662ab613c8748231e069527258fc7d66333007785541a419fcba26a4a2ae9aa54159b3b6891eb06d8cb
|
|
7
|
+
data.tar.gz: ba129a442d7afbb050591903c6b753ad4e0c62360c9c2955b640a039a79f5d8dc42a07210d1c279e651920fd49bdc00f3e0a0bd2458426f9c851015ace4b8111
|
data/docs/components/button.md
CHANGED
|
@@ -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
|
+
```
|
data/docs/messages/flow.md
CHANGED
|
@@ -1,12 +1,248 @@
|
|
|
1
1
|
# Flow
|
|
2
2
|
|
|
3
|
-
Flow is a special type of
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
+
```
|
data/docs/messages/template.md
CHANGED
|
@@ -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/
|
|
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
|
-
|
|
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,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
|
data/lib/warb/resources/flow.rb
CHANGED
|
@@ -3,34 +3,94 @@
|
|
|
3
3
|
module Warb
|
|
4
4
|
module Resources
|
|
5
5
|
class Flow < Resource
|
|
6
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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,7 +3,9 @@
|
|
|
3
3
|
module Warb
|
|
4
4
|
module Resources
|
|
5
5
|
class Template < Resource
|
|
6
|
-
|
|
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
|
|
data/lib/warb/resources/text.rb
CHANGED
|
@@ -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
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.
|
|
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-
|
|
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
|