flow_chat 0.4.0 → 0.4.2
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/Gemfile +1 -0
- data/README.md +408 -102
- data/examples/initializer.rb +1 -1
- data/examples/media_prompts_examples.rb +27 -0
- data/examples/multi_tenant_whatsapp_controller.rb +60 -64
- data/examples/ussd_controller.rb +17 -11
- data/examples/whatsapp_controller.rb +11 -12
- data/examples/whatsapp_media_examples.rb +404 -0
- data/examples/whatsapp_message_job.rb +111 -0
- data/lib/flow_chat/base_processor.rb +8 -4
- data/lib/flow_chat/config.rb +37 -0
- data/lib/flow_chat/session/cache_session_store.rb +5 -5
- data/lib/flow_chat/simulator/controller.rb +78 -0
- data/lib/flow_chat/simulator/views/simulator.html.erb +1982 -0
- data/lib/flow_chat/ussd/gateway/nsano.rb +1 -1
- data/lib/flow_chat/ussd/processor.rb +1 -2
- data/lib/flow_chat/ussd/prompt.rb +39 -5
- data/lib/flow_chat/version.rb +1 -1
- data/lib/flow_chat/whatsapp/app.rb +8 -2
- data/lib/flow_chat/whatsapp/client.rb +435 -0
- data/lib/flow_chat/whatsapp/configuration.rb +50 -12
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +113 -115
- data/lib/flow_chat/whatsapp/middleware/executor.rb +1 -1
- data/lib/flow_chat/whatsapp/processor.rb +1 -11
- data/lib/flow_chat/whatsapp/prompt.rb +125 -84
- data/lib/flow_chat/whatsapp/send_job_support.rb +79 -0
- data/lib/flow_chat/whatsapp/template_manager.rb +7 -7
- metadata +8 -3
- data/lib/flow_chat/ussd/simulator/controller.rb +0 -51
- data/lib/flow_chat/ussd/simulator/views/simulator.html.erb +0 -239
|
@@ -16,7 +16,7 @@ module FlowChat
|
|
|
16
16
|
type: "template",
|
|
17
17
|
template: {
|
|
18
18
|
name: template_name,
|
|
19
|
-
language: {
|
|
19
|
+
language: {code: language},
|
|
20
20
|
components: components
|
|
21
21
|
}
|
|
22
22
|
}
|
|
@@ -27,7 +27,7 @@ module FlowChat
|
|
|
27
27
|
# Common template structures
|
|
28
28
|
def send_welcome_template(to:, name: nil)
|
|
29
29
|
components = []
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
if name
|
|
32
32
|
components << {
|
|
33
33
|
type: "header",
|
|
@@ -87,7 +87,7 @@ module FlowChat
|
|
|
87
87
|
def create_template(name:, category:, language: "en_US", components: [])
|
|
88
88
|
business_account_id = @config.business_account_id
|
|
89
89
|
uri = URI("https://graph.facebook.com/v18.0/#{business_account_id}/message_templates")
|
|
90
|
-
|
|
90
|
+
|
|
91
91
|
template_data = {
|
|
92
92
|
name: name,
|
|
93
93
|
category: category, # AUTHENTICATION, MARKETING, UTILITY
|
|
@@ -111,7 +111,7 @@ module FlowChat
|
|
|
111
111
|
def list_templates
|
|
112
112
|
business_account_id = @config.business_account_id
|
|
113
113
|
uri = URI("https://graph.facebook.com/v18.0/#{business_account_id}/message_templates")
|
|
114
|
-
|
|
114
|
+
|
|
115
115
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
116
116
|
http.use_ssl = true
|
|
117
117
|
|
|
@@ -125,7 +125,7 @@ module FlowChat
|
|
|
125
125
|
# Get template status
|
|
126
126
|
def template_status(template_id)
|
|
127
127
|
uri = URI("https://graph.facebook.com/v18.0/#{template_id}")
|
|
128
|
-
|
|
128
|
+
|
|
129
129
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
130
130
|
http.use_ssl = true
|
|
131
131
|
|
|
@@ -149,7 +149,7 @@ module FlowChat
|
|
|
149
149
|
request.body = message_data.to_json
|
|
150
150
|
|
|
151
151
|
response = http.request(request)
|
|
152
|
-
|
|
152
|
+
|
|
153
153
|
unless response.is_a?(Net::HTTPSuccess)
|
|
154
154
|
Rails.logger.error "WhatsApp Template API error: #{response.body}"
|
|
155
155
|
return nil
|
|
@@ -159,4 +159,4 @@ module FlowChat
|
|
|
159
159
|
end
|
|
160
160
|
end
|
|
161
161
|
end
|
|
162
|
-
end
|
|
162
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: flow_chat
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stefan Froelich
|
|
@@ -98,9 +98,12 @@ files:
|
|
|
98
98
|
- bin/console
|
|
99
99
|
- bin/setup
|
|
100
100
|
- examples/initializer.rb
|
|
101
|
+
- examples/media_prompts_examples.rb
|
|
101
102
|
- examples/multi_tenant_whatsapp_controller.rb
|
|
102
103
|
- examples/ussd_controller.rb
|
|
103
104
|
- examples/whatsapp_controller.rb
|
|
105
|
+
- examples/whatsapp_media_examples.rb
|
|
106
|
+
- examples/whatsapp_message_job.rb
|
|
104
107
|
- flow_chat.gemspec
|
|
105
108
|
- images/ussd_simulator.png
|
|
106
109
|
- lib/flow_chat.rb
|
|
@@ -112,6 +115,8 @@ files:
|
|
|
112
115
|
- lib/flow_chat/session/cache_session_store.rb
|
|
113
116
|
- lib/flow_chat/session/middleware.rb
|
|
114
117
|
- lib/flow_chat/session/rails_session_store.rb
|
|
118
|
+
- lib/flow_chat/simulator/controller.rb
|
|
119
|
+
- lib/flow_chat/simulator/views/simulator.html.erb
|
|
115
120
|
- lib/flow_chat/ussd/app.rb
|
|
116
121
|
- lib/flow_chat/ussd/gateway/nalo.rb
|
|
117
122
|
- lib/flow_chat/ussd/gateway/nsano.rb
|
|
@@ -121,15 +126,15 @@ files:
|
|
|
121
126
|
- lib/flow_chat/ussd/processor.rb
|
|
122
127
|
- lib/flow_chat/ussd/prompt.rb
|
|
123
128
|
- lib/flow_chat/ussd/renderer.rb
|
|
124
|
-
- lib/flow_chat/ussd/simulator/controller.rb
|
|
125
|
-
- lib/flow_chat/ussd/simulator/views/simulator.html.erb
|
|
126
129
|
- lib/flow_chat/version.rb
|
|
127
130
|
- lib/flow_chat/whatsapp/app.rb
|
|
131
|
+
- lib/flow_chat/whatsapp/client.rb
|
|
128
132
|
- lib/flow_chat/whatsapp/configuration.rb
|
|
129
133
|
- lib/flow_chat/whatsapp/gateway/cloud_api.rb
|
|
130
134
|
- lib/flow_chat/whatsapp/middleware/executor.rb
|
|
131
135
|
- lib/flow_chat/whatsapp/processor.rb
|
|
132
136
|
- lib/flow_chat/whatsapp/prompt.rb
|
|
137
|
+
- lib/flow_chat/whatsapp/send_job_support.rb
|
|
133
138
|
- lib/flow_chat/whatsapp/template_manager.rb
|
|
134
139
|
homepage: https://github.com/radioactive-labs/flow_chat
|
|
135
140
|
licenses:
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
module FlowChat
|
|
2
|
-
module Ussd
|
|
3
|
-
module Simulator
|
|
4
|
-
module Controller
|
|
5
|
-
def ussd_simulator
|
|
6
|
-
respond_to do |format|
|
|
7
|
-
format.html do
|
|
8
|
-
render inline: simulator_view_template, layout: false, locals: simulator_locals
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
protected
|
|
14
|
-
|
|
15
|
-
def show_options
|
|
16
|
-
true
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def default_msisdn
|
|
20
|
-
"233200123456"
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def default_endpoint
|
|
24
|
-
"/ussd"
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def default_provider
|
|
28
|
-
:nalo
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def simulator_view_template
|
|
32
|
-
File.read simulator_view_path
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def simulator_view_path
|
|
36
|
-
File.join FlowChat.root.join("flow_chat", "ussd", "simulator", "views", "simulator.html.erb")
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def simulator_locals
|
|
40
|
-
{
|
|
41
|
-
pagesize: FlowChat::Config.ussd.pagination_page_size,
|
|
42
|
-
show_options: show_options,
|
|
43
|
-
default_msisdn: default_msisdn,
|
|
44
|
-
default_endpoint: default_endpoint,
|
|
45
|
-
default_provider: default_provider
|
|
46
|
-
}
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>FlowChat Ussd Simulator</title>
|
|
5
|
-
<style>
|
|
6
|
-
.content {
|
|
7
|
-
width: 320px;
|
|
8
|
-
margin: 100px auto;
|
|
9
|
-
}
|
|
10
|
-
.label {
|
|
11
|
-
display: inline-block;
|
|
12
|
-
width: 80px;
|
|
13
|
-
font-weight: bold;
|
|
14
|
-
}
|
|
15
|
-
.value {
|
|
16
|
-
display: inline;
|
|
17
|
-
}
|
|
18
|
-
.value select, input {
|
|
19
|
-
width: 200px;
|
|
20
|
-
}
|
|
21
|
-
.field {
|
|
22
|
-
margin: 5px;
|
|
23
|
-
}
|
|
24
|
-
#screen {
|
|
25
|
-
border: 1px black solid;
|
|
26
|
-
height:400px;
|
|
27
|
-
width:300px;
|
|
28
|
-
margin-top: 10px;
|
|
29
|
-
margin-bottom: 10px;
|
|
30
|
-
padding: 10px 5px;
|
|
31
|
-
}
|
|
32
|
-
#char-count {
|
|
33
|
-
text-align: center;
|
|
34
|
-
font-size: 10px;
|
|
35
|
-
}
|
|
36
|
-
.hidden {
|
|
37
|
-
display: none;
|
|
38
|
-
}
|
|
39
|
-
</style>
|
|
40
|
-
</head>
|
|
41
|
-
<body>
|
|
42
|
-
<div class="content">
|
|
43
|
-
<div class="field <%= show_options ? '' : 'hidden' %>">
|
|
44
|
-
<div class="label">Provider </div>
|
|
45
|
-
<div class="value">
|
|
46
|
-
<select id="provider">
|
|
47
|
-
<option <%= default_provider == :nalo ? 'selected' : '' %> value="nalo">Nalo</option>
|
|
48
|
-
<option <%= default_provider == :nsano ? 'selected' : '' %> value="nsano">Nsano</option>
|
|
49
|
-
</select>
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
52
|
-
<div class="field <%= show_options ? '' : 'hidden' %>">
|
|
53
|
-
<div class="label">Endpoint </div>
|
|
54
|
-
<div class="value">
|
|
55
|
-
<input id="endpoint" value="<%= default_endpoint %>" />
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
<div class="field <%= show_options ? '' : 'hidden' %>">
|
|
59
|
-
<div class="label">MSISDN </div>
|
|
60
|
-
<div class="value">
|
|
61
|
-
<input id="msisdn" value="<%= default_msisdn %>" />
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
<div id="screen"></div>
|
|
65
|
-
<div id="char-count"></div>
|
|
66
|
-
<div class="field">
|
|
67
|
-
<input id="data" disabled> <button id="respond" disabled>Respond</button>
|
|
68
|
-
</div>
|
|
69
|
-
<div class="field">
|
|
70
|
-
<button id="initiate" disabled>Initiate</button>
|
|
71
|
-
<button id="reset" disabled>Reset</button>
|
|
72
|
-
</div>
|
|
73
|
-
</div>
|
|
74
|
-
<script>
|
|
75
|
-
// Config
|
|
76
|
-
const pagesize = <%= pagesize %>
|
|
77
|
-
|
|
78
|
-
// View
|
|
79
|
-
const $screen = document.getElementById('screen')
|
|
80
|
-
const $charCount = document.getElementById('char-count')
|
|
81
|
-
|
|
82
|
-
const $provider = document.getElementById('provider')
|
|
83
|
-
const $endpoint = document.getElementById('endpoint')
|
|
84
|
-
const $msisdn = document.getElementById('msisdn')
|
|
85
|
-
|
|
86
|
-
const $data = document.getElementById('data')
|
|
87
|
-
const $respondBtn = document.getElementById('respond')
|
|
88
|
-
const $initiateBtn = document.getElementById('initiate')
|
|
89
|
-
const $resetBtn = document.getElementById('reset')
|
|
90
|
-
|
|
91
|
-
$provider.addEventListener('change', function (e) {
|
|
92
|
-
state.provider = $provider.value
|
|
93
|
-
render()
|
|
94
|
-
}, false)
|
|
95
|
-
|
|
96
|
-
$endpoint.addEventListener('keyup', function (e) {
|
|
97
|
-
state.endpoint = $endpoint.value
|
|
98
|
-
render()
|
|
99
|
-
}, false)
|
|
100
|
-
|
|
101
|
-
$msisdn.addEventListener('keyup', function (e) {
|
|
102
|
-
state.msisdn = $msisdn.value
|
|
103
|
-
render()
|
|
104
|
-
}, false)
|
|
105
|
-
|
|
106
|
-
$initiateBtn.addEventListener('click', function (e) {
|
|
107
|
-
makeRequest()
|
|
108
|
-
}, false)
|
|
109
|
-
|
|
110
|
-
$resetBtn.addEventListener('click',function(e){
|
|
111
|
-
reset()
|
|
112
|
-
}, false)
|
|
113
|
-
|
|
114
|
-
$respondBtn.addEventListener('click', function (e) {
|
|
115
|
-
makeRequest()
|
|
116
|
-
}, false)
|
|
117
|
-
|
|
118
|
-
function disableInputs() {
|
|
119
|
-
$data.disabled = 'disabled'
|
|
120
|
-
$respondBtn.disabled = 'disabled'
|
|
121
|
-
$initiateBtn.disabled = 'disabled'
|
|
122
|
-
$resetBtn.disabled = 'disabled'
|
|
123
|
-
$data.disabled = 'disabled'
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function enableResponse() {
|
|
127
|
-
$data.disabled = false
|
|
128
|
-
$respondBtn.disabled = false
|
|
129
|
-
$resetBtn.disabled = false
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function display(text) {
|
|
133
|
-
$screen.innerText = text.substr(0, pagesize)
|
|
134
|
-
if(text.length > 0)
|
|
135
|
-
$charCount.innerText = `${text.length} chars`
|
|
136
|
-
else
|
|
137
|
-
$charCount.innerText = ''
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function render() {
|
|
141
|
-
disableInputs()
|
|
142
|
-
|
|
143
|
-
if(!state.isRunning){
|
|
144
|
-
if(state.provider && state.endpoint && state.msisdn)
|
|
145
|
-
$initiateBtn.disabled = false
|
|
146
|
-
else
|
|
147
|
-
$initiateBtn.disabled = 'disabled'
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
enableResponse()
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// State
|
|
155
|
-
const state = {}
|
|
156
|
-
|
|
157
|
-
function reset(shouldRender) {
|
|
158
|
-
state.isRunning = false
|
|
159
|
-
state.request_id = btoa(Math.random().toString()).substr(10, 10)
|
|
160
|
-
state.provider = $provider.value
|
|
161
|
-
state.endpoint = $endpoint.value
|
|
162
|
-
state.msisdn = $msisdn.value
|
|
163
|
-
|
|
164
|
-
$data.value = null
|
|
165
|
-
|
|
166
|
-
display("")
|
|
167
|
-
if(shouldRender !== false) render()
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
// API
|
|
172
|
-
|
|
173
|
-
function makeRequest() {
|
|
174
|
-
var data = {}
|
|
175
|
-
|
|
176
|
-
switch (state.provider) {
|
|
177
|
-
case "nalo":
|
|
178
|
-
data = {
|
|
179
|
-
USERID: state.request_id,
|
|
180
|
-
MSISDN: state.msisdn,
|
|
181
|
-
USERDATA: $data.value,
|
|
182
|
-
MSGTYPE: !state.isRunning,
|
|
183
|
-
}
|
|
184
|
-
break;
|
|
185
|
-
case "nsano":
|
|
186
|
-
data = {
|
|
187
|
-
network: 'MTN',
|
|
188
|
-
msisdn: state.msisdn,
|
|
189
|
-
msg: $data.value,
|
|
190
|
-
UserSessionID: state.request_id,
|
|
191
|
-
}
|
|
192
|
-
break;
|
|
193
|
-
|
|
194
|
-
default:
|
|
195
|
-
alert(`Unhandled provider request: ${state.provider}`)
|
|
196
|
-
return
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
disableInputs()
|
|
200
|
-
fetch(state.endpoint, {
|
|
201
|
-
method: 'POST',
|
|
202
|
-
headers: {
|
|
203
|
-
'Content-Type': 'application/json'
|
|
204
|
-
},
|
|
205
|
-
redirect: 'error',
|
|
206
|
-
body: JSON.stringify(data)
|
|
207
|
-
})
|
|
208
|
-
.then(response => {
|
|
209
|
-
if (!response.ok) {
|
|
210
|
-
throw Error(`${response.status}: ${response.statusText}`);
|
|
211
|
-
}
|
|
212
|
-
return response.json()
|
|
213
|
-
})
|
|
214
|
-
.then(data => {
|
|
215
|
-
switch (state.provider) {
|
|
216
|
-
case "nalo":
|
|
217
|
-
display(data.MSG)
|
|
218
|
-
state.isRunning = data.MSGTYPE
|
|
219
|
-
break;
|
|
220
|
-
case "nsano":
|
|
221
|
-
display(data.USSDResp.title)
|
|
222
|
-
state.isRunning = data.USSDResp.action == "input"
|
|
223
|
-
break;
|
|
224
|
-
|
|
225
|
-
default:
|
|
226
|
-
alert(`Unhandled provider response: ${state.provider}`)
|
|
227
|
-
return
|
|
228
|
-
}
|
|
229
|
-
$data.value = null
|
|
230
|
-
})
|
|
231
|
-
.catch(error => alert(error.message))
|
|
232
|
-
.finally(render);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// run the app
|
|
236
|
-
reset()
|
|
237
|
-
</script>
|
|
238
|
-
</body>
|
|
239
|
-
</html>
|