flow_chat 0.8.2 → 0.9.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 +4 -4
- data/.cliff.toml +74 -0
- data/.github/workflows/ci.yml +2 -3
- data/.github/workflows/release.yml +56 -0
- data/.standard.yml +4 -0
- data/CHANGELOG.md +22 -0
- data/CLAUDE.md +327 -0
- data/CONTRIBUTING.md +134 -0
- data/Gemfile +1 -0
- data/README.md +290 -105
- data/Rakefile +5 -1
- data/SECURITY.md +42 -349
- data/docs/architecture.md +510 -0
- data/docs/async-background-processing.md +298 -0
- data/docs/configuration.md +556 -226
- data/docs/factory-pattern.md +355 -0
- data/docs/gateway-context-variables.md +171 -0
- data/docs/gateway-development.md +723 -0
- data/docs/getting-started.md +429 -0
- data/docs/instrumentation.md +264 -153
- data/docs/platforms/telegram.md +1013 -0
- data/docs/platforms/ussd.md +693 -0
- data/docs/platforms/whatsapp.md +1395 -0
- data/docs/testing.md +243 -365
- data/examples/custom_session_id_example.rb +119 -0
- data/examples/http_controller.rb +11 -11
- data/examples/intercom_configuration_example.rb +118 -0
- data/examples/intercom_controller.rb +194 -0
- data/examples/multi_tenant_whatsapp_controller.rb +4 -4
- data/examples/ussd_controller.rb +9 -9
- data/examples/whatsapp_controller.rb +2 -2
- data/flow_chat.gemspec +4 -0
- data/lib/flow_chat/{base_app.rb → app.rb} +16 -9
- data/lib/flow_chat/async_job.rb +176 -0
- data/lib/flow_chat/config.rb +10 -30
- data/lib/flow_chat/{base_executor.rb → executor.rb} +6 -11
- data/lib/flow_chat/factory.rb +94 -0
- data/lib/flow_chat/gateway_async_support.rb +106 -0
- data/lib/flow_chat/generic_async_job.rb +30 -0
- data/lib/flow_chat/http/gateway/simple.rb +81 -33
- data/lib/flow_chat/http/renderer.rb +3 -3
- data/lib/flow_chat/instrumentation/setup.rb +1 -1
- data/lib/flow_chat/instrumentation.rb +23 -0
- data/lib/flow_chat/intercom/client.rb +155 -0
- data/lib/flow_chat/intercom/configuration.rb +149 -0
- data/lib/flow_chat/intercom/gateway/intercom_api.rb +396 -0
- data/lib/flow_chat/intercom/renderer.rb +71 -0
- data/lib/flow_chat/phone_number_util.rb +37 -35
- data/lib/flow_chat/processor.rb +188 -0
- data/lib/flow_chat/renderers/markdown_support.rb +58 -0
- data/lib/flow_chat/session/middleware.rb +25 -9
- data/lib/flow_chat/telegram/client.rb +240 -0
- data/lib/flow_chat/telegram/configuration.rb +118 -0
- data/lib/flow_chat/telegram/gateway/bot_api.rb +300 -0
- data/lib/flow_chat/telegram/middleware/choice_mapper.rb +35 -0
- data/lib/flow_chat/telegram/renderer.rb +125 -0
- data/lib/flow_chat/telegram.rb +7 -0
- data/lib/flow_chat/ussd/gateway/nalo.rb +24 -4
- data/lib/flow_chat/ussd/middleware/pagination.rb +9 -5
- data/lib/flow_chat/ussd/renderer.rb +1 -1
- data/lib/flow_chat/version.rb +1 -1
- data/lib/flow_chat/whatsapp/client.rb +144 -13
- data/lib/flow_chat/whatsapp/configuration.rb +1 -1
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +132 -96
- data/lib/flow_chat/whatsapp/id_generator.rb +124 -0
- data/lib/flow_chat/whatsapp/middleware/choice_mapper.rb +147 -0
- data/lib/flow_chat/whatsapp/renderer.rb +145 -11
- data/lib/flow_chat.rb +11 -1
- data/lib/tasks/release.rake +165 -0
- metadata +84 -25
- data/docs/flows.md +0 -320
- data/docs/http-gateway-protocol.md +0 -432
- data/docs/images/simulator.png +0 -0
- data/docs/media.md +0 -153
- data/docs/sessions.md +0 -433
- data/docs/ussd-setup.md +0 -322
- data/docs/whatsapp-setup.md +0 -162
- data/examples/whatsapp_message_job.rb +0 -113
- data/lib/flow_chat/base_processor.rb +0 -146
- data/lib/flow_chat/http/app.rb +0 -6
- data/lib/flow_chat/http/middleware/executor.rb +0 -24
- data/lib/flow_chat/http/processor.rb +0 -33
- data/lib/flow_chat/session/rails_session_store.rb +0 -68
- data/lib/flow_chat/ussd/app.rb +0 -6
- data/lib/flow_chat/ussd/gateway/nsano.rb +0 -96
- data/lib/flow_chat/ussd/middleware/executor.rb +0 -24
- data/lib/flow_chat/ussd/processor.rb +0 -39
- data/lib/flow_chat/whatsapp/app.rb +0 -29
- data/lib/flow_chat/whatsapp/middleware/executor.rb +0 -24
- data/lib/flow_chat/whatsapp/processor.rb +0 -32
- data/lib/flow_chat/whatsapp/send_job_support.rb +0 -79
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
# Factory Pattern
|
|
2
|
+
|
|
3
|
+
The Factory pattern provides centralized processor configuration, eliminating duplication between webhook controllers and background jobs.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### 1. Register Factories
|
|
8
|
+
|
|
9
|
+
Register your processor configurations once in an initializer:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# config/initializers/flow_chat.rb
|
|
13
|
+
|
|
14
|
+
FlowChat::Factory.register :whatsapp do |controller|
|
|
15
|
+
processor = FlowChat::Processor.new(controller) do |config|
|
|
16
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
17
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
18
|
+
config.use_session_config(boundaries: [:flow])
|
|
19
|
+
end
|
|
20
|
+
processor.run(WhatsAppFlow, :start)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
FlowChat::Factory.register :intercom do |controller|
|
|
24
|
+
processor = FlowChat::Processor.new(controller) do |config|
|
|
25
|
+
config.use_gateway FlowChat::Intercom::Gateway::IntercomApi
|
|
26
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
27
|
+
config.use_session_config(boundaries: [:conversation], identifier: :conversation_id)
|
|
28
|
+
end
|
|
29
|
+
processor.run(IntercomFlow, :start)
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. Use in Controllers
|
|
34
|
+
|
|
35
|
+
Execute factories directly in webhook controllers:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
# app/controllers/webhooks_controller.rb
|
|
39
|
+
class WebhooksController < ApplicationController
|
|
40
|
+
def whatsapp
|
|
41
|
+
FlowChat::Factory.execute(:whatsapp, controller: self)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def intercom
|
|
45
|
+
FlowChat::Factory.execute(:intercom, controller: self)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 3. Use with Async (Recommended)
|
|
51
|
+
|
|
52
|
+
Register factory with async support for background processing:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
# config/initializers/flow_chat.rb
|
|
56
|
+
FlowChat::Factory.register :whatsapp do |controller|
|
|
57
|
+
processor = FlowChat::Processor.new(controller) do |config|
|
|
58
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
59
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
60
|
+
config.use_session_config(boundaries: [:flow])
|
|
61
|
+
config.use_async(factory: :whatsapp) # Self-referencing for async
|
|
62
|
+
end
|
|
63
|
+
processor.run(WhatsAppFlow, :start)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# app/controllers/webhooks_controller.rb
|
|
67
|
+
class WebhooksController < ApplicationController
|
|
68
|
+
def whatsapp
|
|
69
|
+
FlowChat::Factory.execute(:whatsapp, controller: self)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**How it works:**
|
|
75
|
+
1. Webhook receives request
|
|
76
|
+
2. `use_async(factory: :whatsapp)` automatically uses `GenericAsyncJob`
|
|
77
|
+
3. Job is enqueued with `factory: :whatsapp` parameter
|
|
78
|
+
4. Background worker executes `FlowChat::Factory.execute(:whatsapp, controller: background_controller)`
|
|
79
|
+
5. Flow processes in background with same configuration
|
|
80
|
+
|
|
81
|
+
## Benefits
|
|
82
|
+
|
|
83
|
+
### 1. Single Source of Truth
|
|
84
|
+
|
|
85
|
+
Define processor configuration once, use everywhere:
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
# Before: Duplicated configuration
|
|
89
|
+
# Webhook controller
|
|
90
|
+
processor = FlowChat::Processor.new(self) do |config|
|
|
91
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
92
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
93
|
+
config.use_session_config(boundaries: [:flow])
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Background job (duplicate!)
|
|
97
|
+
processor = FlowChat::Processor.new(controller) do |config|
|
|
98
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
99
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
100
|
+
config.use_session_config(boundaries: [:flow])
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# After: Single registration
|
|
104
|
+
FlowChat::Factory.register :whatsapp do |controller|
|
|
105
|
+
processor = FlowChat::Processor.new(controller) do |config|
|
|
106
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
107
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
108
|
+
config.use_session_config(boundaries: [:flow])
|
|
109
|
+
end
|
|
110
|
+
processor.run(WhatsAppFlow, :start)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Use everywhere with one line
|
|
114
|
+
FlowChat::Factory.execute(:whatsapp, controller: self)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 2. No Custom Job Classes
|
|
118
|
+
|
|
119
|
+
For simple cases, use `GenericAsyncJob` automatically:
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
# No need to create MyFlowJob class
|
|
123
|
+
config.use_async(factory: :whatsapp)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 3. Context-Aware Execution
|
|
127
|
+
|
|
128
|
+
Same factory works in both webhook and background contexts:
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
# Webhook context - processes inline or enqueues
|
|
132
|
+
FlowChat::Factory.execute(:whatsapp, controller: webhook_controller)
|
|
133
|
+
|
|
134
|
+
# Background context - always processes inline
|
|
135
|
+
FlowChat::Factory.execute(:whatsapp, controller: background_controller)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Gateways automatically detect the context and prevent double-enqueueing.
|
|
139
|
+
|
|
140
|
+
### 4. Easy Testing
|
|
141
|
+
|
|
142
|
+
Test factories in isolation:
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
RSpec.describe "WhatsApp Factory" do
|
|
146
|
+
it "executes successfully" do
|
|
147
|
+
controller = mock_controller
|
|
148
|
+
expect {
|
|
149
|
+
FlowChat::Factory.execute(:whatsapp, controller: controller)
|
|
150
|
+
}.not_to raise_error
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## API Reference
|
|
156
|
+
|
|
157
|
+
### Registration
|
|
158
|
+
|
|
159
|
+
**`FlowChat::Factory.register(name, &block)`**
|
|
160
|
+
|
|
161
|
+
Register a factory with a given name.
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
FlowChat::Factory.register :my_flow do |controller|
|
|
165
|
+
# Build and run processor
|
|
166
|
+
processor = FlowChat::Processor.new(controller) do |config|
|
|
167
|
+
# ... configuration
|
|
168
|
+
end
|
|
169
|
+
processor.run(MyFlow, :start)
|
|
170
|
+
end
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Parameters:**
|
|
174
|
+
- `name` (Symbol): Unique factory identifier
|
|
175
|
+
- `block` (Proc): Factory block receiving `controller`
|
|
176
|
+
|
|
177
|
+
**Returns:** `nil`
|
|
178
|
+
|
|
179
|
+
### Execution
|
|
180
|
+
|
|
181
|
+
**`FlowChat::Factory.execute(name, controller:)`**
|
|
182
|
+
|
|
183
|
+
Execute a registered factory.
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
FlowChat::Factory.execute(:my_flow, controller: self)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Parameters:**
|
|
190
|
+
- `name` (Symbol): Factory name to execute
|
|
191
|
+
- `controller` (Object): Rails controller or BackgroundController
|
|
192
|
+
|
|
193
|
+
**Returns:** Result of factory block
|
|
194
|
+
|
|
195
|
+
**Raises:**
|
|
196
|
+
- `FlowChat::Factory::FactoryNotFoundError` if factory not registered
|
|
197
|
+
|
|
198
|
+
### Introspection
|
|
199
|
+
|
|
200
|
+
**`FlowChat::Factory.registered?(name)`**
|
|
201
|
+
|
|
202
|
+
Check if factory is registered.
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
FlowChat::Factory.registered?(:whatsapp) # => true
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**`FlowChat::Factory.registered_factories`**
|
|
209
|
+
|
|
210
|
+
Get all registered factory names.
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
FlowChat::Factory.registered_factories # => [:whatsapp, :intercom, :ussd]
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**`FlowChat::Factory.clear!`**
|
|
217
|
+
|
|
218
|
+
Clear all registered factories (primarily for testing).
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
FlowChat::Factory.clear!
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Advanced Patterns
|
|
225
|
+
|
|
226
|
+
### Environment-Specific Factories
|
|
227
|
+
|
|
228
|
+
Create different configurations per environment:
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
if Rails.env.production?
|
|
232
|
+
FlowChat::Factory.register :whatsapp do |controller|
|
|
233
|
+
processor = FlowChat::Processor.new(controller) do |config|
|
|
234
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
235
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
236
|
+
end
|
|
237
|
+
processor.run(ProductionFlow, :start)
|
|
238
|
+
end
|
|
239
|
+
else
|
|
240
|
+
FlowChat::Factory.register :whatsapp do |controller|
|
|
241
|
+
processor = FlowChat::Processor.new(controller) do |config|
|
|
242
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
243
|
+
config.use_session_store FlowChat::Session::RailsSessionStore
|
|
244
|
+
end
|
|
245
|
+
processor.run(DevelopmentFlow, :start)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Multiple Variants
|
|
251
|
+
|
|
252
|
+
Register multiple factories for the same platform:
|
|
253
|
+
|
|
254
|
+
```ruby
|
|
255
|
+
FlowChat::Factory.register :whatsapp_support do |controller|
|
|
256
|
+
processor = FlowChat::Processor.new(controller) do |config|
|
|
257
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
258
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
259
|
+
end
|
|
260
|
+
processor.run(SupportFlow, :start)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
FlowChat::Factory.register :whatsapp_sales do |controller|
|
|
264
|
+
processor = FlowChat::Processor.new(controller) do |config|
|
|
265
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
266
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
267
|
+
end
|
|
268
|
+
processor.run(SalesFlow, :start)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Use based on routing
|
|
272
|
+
def whatsapp
|
|
273
|
+
factory = params[:department] == 'sales' ? :whatsapp_sales : :whatsapp_support
|
|
274
|
+
FlowChat::Factory.execute(factory, controller: self)
|
|
275
|
+
end
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Accessing Request Data
|
|
279
|
+
|
|
280
|
+
Factories receive the controller, which includes request data:
|
|
281
|
+
|
|
282
|
+
```ruby
|
|
283
|
+
FlowChat::Factory.register :whatsapp do |controller|
|
|
284
|
+
# Access request params
|
|
285
|
+
user_id = controller.params[:user_id]
|
|
286
|
+
platform = controller.request.headers["User-Agent"]
|
|
287
|
+
|
|
288
|
+
processor = FlowChat::Processor.new(controller) do |config|
|
|
289
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
290
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Route to different flows based on params
|
|
294
|
+
flow = user_id.start_with?('premium_') ? PremiumFlow : StandardFlow
|
|
295
|
+
processor.run(flow, :start)
|
|
296
|
+
end
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Migration Guide
|
|
300
|
+
|
|
301
|
+
### From Duplicate Configuration
|
|
302
|
+
|
|
303
|
+
**Before:**
|
|
304
|
+
```ruby
|
|
305
|
+
# Webhook controller
|
|
306
|
+
class WebhooksController < ApplicationController
|
|
307
|
+
def whatsapp
|
|
308
|
+
processor = FlowChat::Processor.new(self) do |config|
|
|
309
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
310
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
311
|
+
config.use_async(WhatsAppFlowJob)
|
|
312
|
+
end
|
|
313
|
+
processor.run(WhatsAppFlow, :start)
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Background job (duplicate!)
|
|
318
|
+
class WhatsAppFlowJob < FlowChat::AsyncJob
|
|
319
|
+
def execute(controller, **job_params)
|
|
320
|
+
processor = FlowChat::Processor.new(controller) do |config|
|
|
321
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
322
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
323
|
+
end
|
|
324
|
+
processor.run(WhatsAppFlow, :start)
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**After:**
|
|
330
|
+
```ruby
|
|
331
|
+
# config/initializers/flow_chat.rb
|
|
332
|
+
FlowChat::Factory.register :whatsapp do |controller|
|
|
333
|
+
processor = FlowChat::Processor.new(controller) do |config|
|
|
334
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
335
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
336
|
+
config.use_async(factory: :whatsapp) # Self-referencing for async
|
|
337
|
+
end
|
|
338
|
+
processor.run(WhatsAppFlow, :start)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Webhook controller - ONE LINE!
|
|
342
|
+
class WebhooksController < ApplicationController
|
|
343
|
+
def whatsapp
|
|
344
|
+
FlowChat::Factory.execute(:whatsapp, controller: self)
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# No custom job class needed!
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## See Also
|
|
352
|
+
|
|
353
|
+
- [Async Background Processing](async-background-processing.md) - Using factories with async jobs
|
|
354
|
+
- [Session Management](session-management.md) - Configuring session stores in factories
|
|
355
|
+
- [Gateways](gateways.md) - Platform-specific gateway configuration
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Gateway Context Variables
|
|
2
|
+
|
|
3
|
+
This document describes all context variables set by each gateway in FlowChat.
|
|
4
|
+
|
|
5
|
+
## All Context Variables
|
|
6
|
+
|
|
7
|
+
| Variable | USSD Nalo | HTTP Simple | WhatsApp Cloud API | Telegram Bot API | Intercom API | Description |
|
|
8
|
+
|----------|-----------|-------------|-------------------|------------------|--------------|-------------|
|
|
9
|
+
| **Common Variables** |
|
|
10
|
+
| `request.id` | ✓ Session ID | ✓ From user_params | ✓ Phone number | ✓ Chat ID | ✓ Conversation ID | Unique identifier for the session/conversation |
|
|
11
|
+
| `request.user_id` | ✓ = msisdn | ✓ From user_params | ✓ Phone number | ✓ Telegram user ID | ✓ Contact ID | User/contact identifier |
|
|
12
|
+
| `request.user_name` | ✗ | ✓ (optional) | ✓ (optional) | ✓ First + Last name | ✓ (optional) | User's display name |
|
|
13
|
+
| `request.username` | ✗ | ✗ | ✗ | ✓ Telegram username | ✗ | Telegram @username |
|
|
14
|
+
| `request.msisdn` | ✓ | ✓ (optional) | ✓ | ✗ | ✓ (optional) | E.164 phone number |
|
|
15
|
+
| `request.email` | ✗ | ✓ (optional) | ✗ | ✗ | ✓ (optional) | User email |
|
|
16
|
+
| `request.message_id` | ✓ UUID | ✓ UUID | ✓ WhatsApp ID | ✓ Telegram msg ID | ✓ (optional) | Message identifier |
|
|
17
|
+
| `request.timestamp` | ✓ Current | ✓ Current | ✓ Current | ✓ From message | ✓ Current | ISO8601 timestamp |
|
|
18
|
+
| `request.gateway` | ✓ `:nalo` | ✓ `:http_simple` | ✓ `:whatsapp_cloud_api` | ✓ `:telegram_bot_api` | ✓ `:intercom_api` | Gateway name |
|
|
19
|
+
| `request.platform` | ✓ `:ussd` | ✓ `:http` | ✓ `:whatsapp` | ✓ `:telegram` | ✓ `:intercom` | Platform type |
|
|
20
|
+
| `request.body` | ✓ | ✓ | ✓ | ✓ | ✓ | Raw request body (stringified keys) |
|
|
21
|
+
| `request.input` | ✓ Text | ✓ Text | ✓ Varies⁴ | ✓ Varies⁶ | ✓ Text/nil⁵ | User's input message |
|
|
22
|
+
| **WhatsApp-Specific** |
|
|
23
|
+
| `request.location` | ✗ | ✗ | ✓ | ✓ | ✗ | Location data (when input is `"$location$"`) |
|
|
24
|
+
| `request.media` | ✗ | ✗ | ✓ | ✓ | ✗ | Media metadata (when input is `"$media$"`) |
|
|
25
|
+
| `request.contact` | ✗ | ✗ | ✗ | ✓ | ✗ | Contact data (when input is `"$contact$"`) |
|
|
26
|
+
| `whatsapp.business.phone_number` | ✗ | ✗ | ✓ | ✗ | ✗ | Business phone number (E.164) |
|
|
27
|
+
| `whatsapp.business.phone_number_id` | ✗ | ✗ | ✓ | ✗ | ✗ | WhatsApp phone number ID |
|
|
28
|
+
| `whatsapp.client` | ✗ | ✗ | ✓ | ✗ | ✗ | WhatsApp client instance |
|
|
29
|
+
| **Telegram-Specific** |
|
|
30
|
+
| `telegram.client` | ✗ | ✗ | ✗ | ✓ | ✗ | Telegram client instance |
|
|
31
|
+
| `telegram.chat_type` | ✗ | ✗ | ✗ | ✓ | ✗ | Chat type (private, group, supergroup, channel) |
|
|
32
|
+
| `telegram.callback_query_id` | ✗ | ✗ | ✗ | ✓ (callbacks) | ✗ | Callback query ID for inline keyboard responses |
|
|
33
|
+
| `telegram.original_message_id` | ✗ | ✗ | ✗ | ✓ (callbacks) | ✗ | Original message ID that triggered callback |
|
|
34
|
+
| **HTTP-Specific** |
|
|
35
|
+
| `http.method` | ✗ | ✓ | ✗ | ✗ | ✗ | HTTP method (GET/POST) |
|
|
36
|
+
| `http.path` | ✗ | ✓ | ✗ | ✗ | ✗ | Request path |
|
|
37
|
+
| `http.user_agent` | ✗ | ✓ | ✗ | ✗ | ✗ | User agent header |
|
|
38
|
+
| **Intercom-Specific** |
|
|
39
|
+
| `intercom.client` | ✗ | ✗ | ✗ | ✗ | ✓ | Intercom client instance |
|
|
40
|
+
| `intercom.topic` | ✗ | ✗ | ✗ | ✗ | ✓ | Webhook event type |
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
## Accessing Variables in Flows
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
class MyFlow < FlowChat::Flow
|
|
47
|
+
def start
|
|
48
|
+
# Common variables (all gateways)
|
|
49
|
+
user_id = app.context["request.user_id"]
|
|
50
|
+
user_name = app.context["request.user_name"] # Available from WhatsApp, Intercom, HTTP (optional)
|
|
51
|
+
msisdn = app.context["request.msisdn"] # Available from USSD, WhatsApp, HTTP (optional)
|
|
52
|
+
email = app.context["request.email"] # Available from HTTP (optional)
|
|
53
|
+
platform = app.context["request.platform"]
|
|
54
|
+
input = app.context["request.input"]
|
|
55
|
+
|
|
56
|
+
# Or use convenience methods
|
|
57
|
+
user_id = app.user_id
|
|
58
|
+
platform = app.platform
|
|
59
|
+
input = app.input
|
|
60
|
+
|
|
61
|
+
# Platform-specific variables
|
|
62
|
+
case app.platform
|
|
63
|
+
when :whatsapp
|
|
64
|
+
client = app.context["whatsapp.client"]
|
|
65
|
+
|
|
66
|
+
# Handle special input types
|
|
67
|
+
if input == "$location$"
|
|
68
|
+
location = app.context["request.location"]
|
|
69
|
+
lat = location[:latitude]
|
|
70
|
+
lng = location[:longitude]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
when :telegram
|
|
74
|
+
client = app.context["telegram.client"]
|
|
75
|
+
chat_type = app.context["telegram.chat_type"]
|
|
76
|
+
username = app.context["request.username"] # @username
|
|
77
|
+
|
|
78
|
+
# Handle special input types
|
|
79
|
+
case input
|
|
80
|
+
when "$location$"
|
|
81
|
+
location = app.context["request.location"]
|
|
82
|
+
lat = location["latitude"]
|
|
83
|
+
lng = location["longitude"]
|
|
84
|
+
when "$media$"
|
|
85
|
+
media = app.context["request.media"]
|
|
86
|
+
file_id = media[:file_id]
|
|
87
|
+
media_type = media[:type] # :photo, :document, :voice
|
|
88
|
+
when "$contact$"
|
|
89
|
+
contact = app.context["request.contact"]
|
|
90
|
+
phone = contact[:phone_number]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
when :intercom
|
|
94
|
+
topic = app.context["intercom.topic"]
|
|
95
|
+
client = app.context["intercom.client"]
|
|
96
|
+
|
|
97
|
+
# Handle events without messages
|
|
98
|
+
if input.nil?
|
|
99
|
+
# Event without user message (e.g., admin-initiated)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
when :http
|
|
103
|
+
method = app.context["http.method"]
|
|
104
|
+
user_agent = app.context["http.user_agent"]
|
|
105
|
+
|
|
106
|
+
when :ussd
|
|
107
|
+
msisdn = app.context["request.msisdn"]
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Notes
|
|
114
|
+
|
|
115
|
+
⁴ **WhatsApp input**: Text for text messages, `"$location$"` for location, `"$media$"` for media (image, document, audio, video, sticker), `"$contact$"` for shared contacts, or button/list reply IDs.
|
|
116
|
+
|
|
117
|
+
⁵ **Intercom input**: Text content or `nil` for events without user messages.
|
|
118
|
+
|
|
119
|
+
⁶ **Telegram input**: Text for text messages, callback data for inline keyboard responses, `"$location$"` for location, `"$media$"` for media (photo, video, audio, document, voice, sticker), `"$contact$"` for shared contacts.
|
|
120
|
+
|
|
121
|
+
## Media Type Reference
|
|
122
|
+
|
|
123
|
+
Both WhatsApp and Telegram set `request.media` with a `:type` symbol when media is received:
|
|
124
|
+
|
|
125
|
+
| Media Type | WhatsApp | Telegram | Additional Fields |
|
|
126
|
+
|------------|----------|----------|-------------------|
|
|
127
|
+
| `:image` / `:photo` | ✓ `:image` | ✓ `:photo` | id/file_id, mime_type, width, height |
|
|
128
|
+
| `:video` | ✓ | ✓ | id/file_id, mime_type, duration, width, height |
|
|
129
|
+
| `:audio` | ✓ | ✓ | id/file_id, mime_type, duration, title, performer |
|
|
130
|
+
| `:voice` | ✗ | ✓ | file_id, mime_type, duration |
|
|
131
|
+
| `:document` | ✓ | ✓ | id/file_id, mime_type, filename |
|
|
132
|
+
| `:sticker` | ✓ | ✓ | id/file_id, emoji, set_name, is_animated |
|
|
133
|
+
|
|
134
|
+
### Accessing Media in Flows
|
|
135
|
+
|
|
136
|
+
FlowChat provides constants for special input markers:
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
FlowChat::Input::LOCATION # "$location$"
|
|
140
|
+
FlowChat::Input::MEDIA # "$media$"
|
|
141
|
+
FlowChat::Input::CONTACT # "$contact$"
|
|
142
|
+
FlowChat::Input::START # "$start$" (session marker)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Example usage:
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
case app.input
|
|
149
|
+
when FlowChat::Input::MEDIA
|
|
150
|
+
media = app.context["request.media"]
|
|
151
|
+
|
|
152
|
+
case media[:type]
|
|
153
|
+
when :photo, :image
|
|
154
|
+
file_id = media[:file_id] || media[:id]
|
|
155
|
+
when :video
|
|
156
|
+
duration = media[:duration]
|
|
157
|
+
when :document
|
|
158
|
+
filename = media[:file_name] || media[:filename]
|
|
159
|
+
when :sticker
|
|
160
|
+
emoji = media[:emoji]
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
when FlowChat::Input::LOCATION
|
|
164
|
+
location = app.context["request.location"]
|
|
165
|
+
lat, lng = location["latitude"], location["longitude"]
|
|
166
|
+
|
|
167
|
+
when FlowChat::Input::CONTACT
|
|
168
|
+
contact = app.context["request.contact"]
|
|
169
|
+
phone = contact[:phone_number]
|
|
170
|
+
end
|
|
171
|
+
```
|