stealth-smooch 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6e3657bee135d475584a394c1a2248f356d0e9173a49f6e941f1b162b92b2069
4
+ data.tar.gz: d19f031c8632ae4b3db4bfc9797f303e084b7bc7fe6d5bb7847d945102409180
5
+ SHA512:
6
+ metadata.gz: fef040a938acf8c730b36351812fbcbbc799406ca81e79a2f3e3360ff641f00fefb37004d8ef3f3ecb2187ed116cdb1f8b2a6a6da064a166d11516961ab42193
7
+ data.tar.gz: 61f7ebbb9e1eb16de0fa5234bafea9111cee6cc76f19efe12711d0c380f8c19c443001d52900aa8b26f9f1176e6c57040bfd269f3a40c684f19299a363fdb67c
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ # please add general patterns to your global ignore list
2
+ # see https://github.com/github/gitignore#readme
3
+ .DS_STORE
4
+ *.swp
5
+ *.rbc
6
+ *.sass-cache
7
+ /pkg
8
+ /doc/api
9
+ /coverage
10
+ .yardoc
11
+ doc/
12
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,133 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ stealth-smooch (0.9.0)
5
+ jwt (~> 2.1)
6
+ smooch-api (~> 4.0)
7
+ stealth (< 2.0)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ actionpack (5.2.1)
13
+ actionview (= 5.2.1)
14
+ activesupport (= 5.2.1)
15
+ rack (~> 2.0)
16
+ rack-test (>= 0.6.3)
17
+ rails-dom-testing (~> 2.0)
18
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
19
+ actionview (5.2.1)
20
+ activesupport (= 5.2.1)
21
+ builder (~> 3.1)
22
+ erubi (~> 1.4)
23
+ rails-dom-testing (~> 2.0)
24
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
25
+ activemodel (5.2.1)
26
+ activesupport (= 5.2.1)
27
+ activerecord (5.2.1)
28
+ activemodel (= 5.2.1)
29
+ activesupport (= 5.2.1)
30
+ arel (>= 9.0)
31
+ activesupport (5.2.1)
32
+ concurrent-ruby (~> 1.0, >= 1.0.2)
33
+ i18n (>= 0.7, < 2)
34
+ minitest (~> 5.1)
35
+ tzinfo (~> 1.1)
36
+ arel (9.0.0)
37
+ builder (3.2.3)
38
+ concurrent-ruby (1.1.3)
39
+ connection_pool (2.2.2)
40
+ crass (1.0.4)
41
+ diff-lcs (1.3)
42
+ erubi (1.7.1)
43
+ ethon (0.11.0)
44
+ ffi (>= 1.3.0)
45
+ ffi (1.9.25)
46
+ i18n (1.1.1)
47
+ concurrent-ruby (~> 1.0)
48
+ json (1.8.6)
49
+ jwt (2.1.0)
50
+ loofah (2.2.3)
51
+ crass (~> 1.0.2)
52
+ nokogiri (>= 1.5.9)
53
+ method_source (0.9.2)
54
+ mini_portile2 (2.3.0)
55
+ minitest (5.11.3)
56
+ multi_json (1.13.1)
57
+ mustermann (1.0.3)
58
+ nokogiri (1.8.5)
59
+ mini_portile2 (~> 2.3.0)
60
+ puma (3.12.0)
61
+ rack (2.0.6)
62
+ rack-protection (2.0.4)
63
+ rack
64
+ rack-test (1.1.0)
65
+ rack (>= 1.0, < 3)
66
+ rails-dom-testing (2.0.3)
67
+ activesupport (>= 4.2.0)
68
+ nokogiri (>= 1.6)
69
+ rails-html-sanitizer (1.0.4)
70
+ loofah (~> 2.2, >= 2.2.2)
71
+ railties (5.2.1)
72
+ actionpack (= 5.2.1)
73
+ activesupport (= 5.2.1)
74
+ method_source
75
+ rake (>= 0.8.7)
76
+ thor (>= 0.19.0, < 2.0)
77
+ rake (12.3.1)
78
+ redis (4.0.3)
79
+ rspec (3.8.0)
80
+ rspec-core (~> 3.8.0)
81
+ rspec-expectations (~> 3.8.0)
82
+ rspec-mocks (~> 3.8.0)
83
+ rspec-core (3.8.0)
84
+ rspec-support (~> 3.8.0)
85
+ rspec-expectations (3.8.1)
86
+ diff-lcs (>= 1.2.0, < 2.0)
87
+ rspec-support (~> 3.8.0)
88
+ rspec-mocks (3.8.0)
89
+ diff-lcs (>= 1.2.0, < 2.0)
90
+ rspec-support (~> 3.8.0)
91
+ rspec-support (3.8.0)
92
+ rspec_junit_formatter (0.4.1)
93
+ rspec-core (>= 2, < 4, != 2.12.0)
94
+ sidekiq (5.2.3)
95
+ connection_pool (~> 2.2, >= 2.2.2)
96
+ rack-protection (>= 1.5.0)
97
+ redis (>= 3.3.5, < 5)
98
+ sinatra (2.0.4)
99
+ mustermann (~> 1.0)
100
+ rack (~> 2.0)
101
+ rack-protection (= 2.0.4)
102
+ tilt (~> 2.0)
103
+ smooch-api (4.0)
104
+ json (~> 1.8, >= 1.8.3)
105
+ typhoeus (~> 1.0, >= 1.0.1)
106
+ stealth (1.0.4)
107
+ activerecord (~> 5.2)
108
+ activesupport (~> 5.2)
109
+ multi_json (~> 1.12)
110
+ puma (~> 3.10)
111
+ railties (~> 5.2)
112
+ sidekiq (~> 5.0)
113
+ sinatra (~> 2.0)
114
+ thor (~> 0.20)
115
+ thor (0.20.3)
116
+ thread_safe (0.3.6)
117
+ tilt (2.0.8)
118
+ typhoeus (1.3.1)
119
+ ethon (>= 0.9.0)
120
+ tzinfo (1.2.5)
121
+ thread_safe (~> 0.1)
122
+
123
+ PLATFORMS
124
+ ruby
125
+
126
+ DEPENDENCIES
127
+ rack-test (~> 1.1)
128
+ rspec (~> 3.6)
129
+ rspec_junit_formatter (~> 0.3)
130
+ stealth-smooch!
131
+
132
+ BUNDLED WITH
133
+ 1.16.6
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2018 Mauricio Gomes, Black Ops Bureau
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,335 @@
1
+ # Stealth Smooch
2
+
3
+ This integration adds support for [Smooch](https://smooch.io) powered bots within [Stealth](https://github.com/hellostealth/stealth). It can be used as a drop-in replacement for `stealth-facebook` with the exception of some specialized quick reply buttons (such as Email & Phone).
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/stealth-smooch.svg)](https://badge.fury.io/rb/stealth-smooch)
6
+
7
+ ## Create Your Smooch App
8
+
9
+ Via the Smooch interface, create a new Smooch app for your Stealth bot. Once you do, you'll be given your `SMOOCH_APP_ID`.
10
+
11
+ Follow the instructions on the [smooch-api Ruby](https://github.com/smooch/smooch-ruby) page to generate your secret keys. This will get you your
12
+ `SMOOCH_KEY_ID` and `SMOOCH_SECRET`.
13
+
14
+ The last thing you will need is the `SMOOCH_JWT_TOKEN` which can be generated using this gem. After you have set the above creds to your `services.yml` file, from your bot's console, run:
15
+
16
+ ```ruby
17
+ Stealth::Services::Smooch::Client.generate_jwt_token
18
+ ```
19
+
20
+ It will output the JWT token based on the `app_id`, `key_id`, and `secret` from your `services.yml` file.
21
+
22
+ ## Configure the Integration
23
+
24
+ ```yaml
25
+ default: &default
26
+ smooch:
27
+ app_id: <%= ENV['SMOOCH_APP_ID'] %>
28
+ key_id: <%= ENV['SMOOCH_KEY_ID'] %>
29
+ secret: <%= ENV['SMOOCH_SECRET'] %>
30
+ jwt_token: <%= ENV['SMOOCH_JWT_TOKEN'] %>
31
+ setup:
32
+ persistent_menu:
33
+ - type: 'url'
34
+ url: 'https://mywebsite.com'
35
+ text: 'About Us'
36
+ - type: 'payload'
37
+ payload: 'contact_support'
38
+ text: 'Contact Support'
39
+
40
+ production:
41
+ <<: *default
42
+
43
+ development:
44
+ <<: *default
45
+
46
+ test:
47
+ <<: *default
48
+ ```
49
+
50
+ Additionally, you will need to create an initializer called `smooch.rb` in `config/initializers`:
51
+
52
+ ```ruby
53
+ SmoochApi.configure do |config|
54
+ config.api_key['Authorization'] = Stealth.config.smooch.jwt_token
55
+ config.api_key_prefix['Authorization'] = 'Bearer'
56
+ end
57
+ ```
58
+
59
+ As with all Stealth integrations, integrations can be specified by environment.
60
+
61
+ These are the supported setup options:
62
+
63
+ ### persistent_menu
64
+
65
+ The persistent menu is not supported by all integrations. For a complete list, please check out the [Smooch Pesistent Menu Docs](https://docs.smooch.io/rest/#persistent-menus).
66
+
67
+ Setting the persistent menu is identical to creating buttons in text replies. Please see those docs for more info.
68
+
69
+ ### Webhooks
70
+
71
+ In order for your bot to receive messages from the Smooch app, we'll need to register our webhooks.
72
+
73
+ Set `SMOOCH_ENDPOINT` to the endpoint that will be receiving the hooks. It's configured as an ENV variable so you can specify different endpoints for each of your environments.
74
+
75
+ After you have set `SMOOCH_ENDPOINT`, running setup below will register your webhooks.
76
+
77
+ ### Running Setup
78
+
79
+ This will set the persistent menu (if available) and register your webhooks.
80
+
81
+ ```
82
+ stealth setup smooch
83
+ ```
84
+
85
+ ## Replies
86
+
87
+ Here are the supported replies for the Smooch integration:
88
+
89
+ ### text
90
+
91
+ These are standard text replies.
92
+
93
+ ```yaml
94
+ - reply_type: text
95
+ text: Hello World!
96
+ ```
97
+
98
+ Text replies can also include suggestions, which will be rendered as quick replies:
99
+
100
+ ```yaml
101
+ - reply_type: text
102
+ text: What is your favorite color?
103
+ suggestions:
104
+ - text: Blue
105
+ - text: Red
106
+ ```
107
+
108
+ Although not as common, text replies can also include buttons:
109
+
110
+ ```yaml
111
+ - reply_type: text
112
+ text: Would you like to give us a call?
113
+ buttons:
114
+ - type: payload
115
+ text: 'Yes'
116
+ payload: 'Yes'
117
+ - type: payload
118
+ text: 'No'
119
+ payload: 'No'
120
+ ```
121
+
122
+ ### suggestions
123
+
124
+ Though suggestions are not a reply type on their own, they are frequently used to optimize the accuracy and speed of your bot. In the `text` reply type above, we used simple labels for our suggestions. Smooch supports a few special types of quick replies, however.
125
+
126
+ #### Location
127
+
128
+ You can ask a user for their location:
129
+
130
+ ```yaml
131
+ - reply_type: text
132
+ text: "Where are you located?"
133
+ suggestions:
134
+ - type: location
135
+ ```
136
+
137
+ If the user chooses to share their location, the `lat` and `lng` will be available via `current_message.location`:
138
+
139
+ ```ruby
140
+ current_message.location[:lat]
141
+ current_message.location[:lng]
142
+ ```
143
+
144
+ #### Images
145
+
146
+ While images are not a special quick reply type, you can include and `image_url` for a quick reply as way of adding an icon to a quick reply button:
147
+
148
+ ```yaml
149
+ - reply_type: text
150
+ text: "What is your favorite color?"
151
+ suggestions:
152
+ - text: Red
153
+ image_url: "http://example.com/img/red.png"
154
+ - text: Blue
155
+ image_url: "http://example.com/img/blue.png"
156
+ ```
157
+
158
+ More info [here](https://docs.smooch.io/rest/#reply).
159
+
160
+ ### buttons
161
+
162
+ As with `suggestions`, `buttons` are not a reply type of their own but are used to make your bot more efficient. Smooch supports a few button types and these are the ones currently supported by this integration:
163
+
164
+ #### payload
165
+
166
+ This is the most common button type. When a user presses a button that is `payload` type, that payload string will be sent to your bot. For example:
167
+
168
+ ```yaml
169
+ - reply_type: text
170
+ text: Please press the button below
171
+ buttons:
172
+ - type: payload
173
+ text: 'Press me!'
174
+ payload: 'button pressed'
175
+
176
+ ```
177
+
178
+ When a user presses the button labeled "Press me!", the payload `button pressed` will be accessible in bot via `current_message.payload`.
179
+
180
+ #### url
181
+
182
+ The `url` button is useful when sharing a link to a website. By default, it will open up within Facebook Messenger.
183
+
184
+ ```yaml
185
+ - reply_type: text
186
+ text: Find out more via our website
187
+ buttons:
188
+ - type: url
189
+ text: 'Visit website'
190
+ url: 'https://example.org'
191
+
192
+ ```
193
+
194
+ ### Delay
195
+
196
+ Delays are a very important part of bot design. They introduce a pause between text replies to give the user a chance to read each reply. With this integration, in addition to introducing a delay, we will also send a typing indicator to the user to indicate another reply is forthcoming. To insert a delay in your bot:
197
+
198
+ ```yaml
199
+ - reply_type: delay
200
+ duration: 2
201
+ ```
202
+
203
+ This will add a `2` second delay (with typing indicator). The `duration` can be specified as any floating point value, in seconds.
204
+
205
+ ### Cards
206
+
207
+ Smooch distinguishes between a single card and a carousel of cards. This integration does not, however. You can send a single card the same way you would send 10 cards (the current maximum).
208
+
209
+ ```yaml
210
+ - reply_type: cards
211
+ elements:
212
+ - title: My App
213
+ subtitle: Download our app below or visit our website for more info.
214
+ image_url: "https://my-app.com/app-image.png"
215
+ buttons:
216
+ - type: url
217
+ url: "https://my-app.com"
218
+ text: 'View'
219
+ webview_height: 'tall'
220
+ - type: url
221
+ url: "https://itunes.apple.com/us/app/my-app"
222
+ text: 'Download iOS App'
223
+ ```
224
+
225
+ The above is a single card with two buttons. If you want to include more cards, though, you would just need to specify another listing under the `elements` heading.
226
+
227
+ More info about Smooch cards [here](https://docs.smooch.io/rest/#carousel).
228
+
229
+ ### List
230
+
231
+ A Smooch list is useful for displaying things like a news feed. You can find more info about Smooch lists [here](https://docs.smooch.io/rest/#list).
232
+
233
+ To generate a list:
234
+
235
+ ```yaml
236
+ - reply_type: list
237
+ buttons:
238
+ - type: payload
239
+ text: View More
240
+ payload: view_more
241
+ elements:
242
+ - title: Your Daily News Update
243
+ subtitle: The following stories have been curated just for you.
244
+ image_url: "https://picsum.photos/320/240"
245
+ buttons:
246
+ - type: url
247
+ url: "https://news-articles.com/199"
248
+ text: 'View'
249
+ - title: Breakthrough in AI
250
+ subtitle: Major breakthrough in the AI space.
251
+ image_url: "https://picsum.photos/320/320"
252
+ buttons:
253
+ - type: url
254
+ url: "https://news-articles.com/201"
255
+ text: 'View'
256
+ ```
257
+
258
+ The list itself supports having a single button that will be rendered on the bottom of the list. Each individual list item supports having one button as well. List items should have between 2-4 elements.
259
+
260
+ More info about Smooch lists [here](https://docs.smooch.io/rest/#list).
261
+
262
+ ### Images
263
+
264
+ To send an image:
265
+
266
+ ```yaml
267
+ - reply_type: image
268
+ image_url: 'https://example.org/image.png'
269
+ ```
270
+
271
+ The `image_url` should be set to URL where the image has been uploaded.
272
+
273
+ Image replies support buttons and suggestions like text replies.
274
+
275
+ ### Files
276
+
277
+ To send a file:
278
+
279
+ ```yaml
280
+ - reply_type: file
281
+ file_url: 'https://example.org/some.pdf'
282
+ ```
283
+
284
+ The `file_url` should be set to URL where the file has been uploaded.
285
+
286
+ File replies support buttons and suggestions like text replies.
287
+
288
+ ### Video
289
+
290
+ To send a video:
291
+
292
+ ```yaml
293
+ - reply_type: video
294
+ video_url: 'https://example.org/cool_video.mp4'
295
+ ```
296
+
297
+ The `video_url` should be set to URL where the video has been uploaded.
298
+
299
+ Video replies support buttons and suggestions like text replies.
300
+
301
+ ### Audio
302
+
303
+ To send an audio clip:
304
+
305
+ ```yaml
306
+ - reply_type: audio
307
+ audio_url: 'https://example.org/podcast.mp3'
308
+ ```
309
+
310
+ The `audio_url` should be set to URL where the video has been uploaded.
311
+
312
+ Audio replies support buttons and suggestions like text replies.
313
+
314
+ ## Development
315
+
316
+ When adding features to this library, you might find it helpful to get a full printout of the HTTP requests and responses from Smooch.
317
+
318
+ In order to configure your bot to show the debug output, modify your `smooch.rb` initializer like so:
319
+
320
+ ```ruby
321
+ class SmoochLogger
322
+
323
+ def self.debug(msg)
324
+ Stealth::Logger.l(topic: 'smooch', message: msg)
325
+ end
326
+
327
+ end
328
+
329
+ SmoochApi.configure do |config|
330
+ config.logger = SmoochLogger
331
+ config.debugging = true
332
+ config.api_key['Authorization'] = Stealth.config.smooch.jwt_token
333
+ config.api_key_prefix['Authorization'] = 'Bearer'
334
+ end
335
+ ```
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.9.0
@@ -0,0 +1,2 @@
1
+ require 'smooch-api'
2
+ require 'stealth/smooch'
@@ -0,0 +1,82 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'stealth/services/smooch/message_handler'
5
+ require 'stealth/services/smooch/reply_handler'
6
+ require 'stealth/services/smooch/setup'
7
+
8
+ module Stealth
9
+ module Services
10
+ module Smooch
11
+
12
+ class Client < Stealth::Services::BaseClient
13
+
14
+ attr_reader :reply
15
+
16
+ def initialize(reply:)
17
+ @reply = reply
18
+ @smooch = SmoochApi::ConversationApi.new
19
+ end
20
+
21
+ def transmit
22
+ begin
23
+ response = @smooch.send(
24
+ reply[:reply_type],
25
+ Stealth.config.smooch.app_id,
26
+ reply[:recipient_id],
27
+ reply[:message]
28
+ )
29
+ rescue SmoochApi::ApiError => e
30
+ msg = Stealth::Logger.colorize('[Error]', color: :red) + " #{e.code}: #{e.response_body}"
31
+ Stealth::Logger.l(topic: 'smooch', message: msg)
32
+ raise Stealth::Errors::ServiceError
33
+ end
34
+
35
+ if response.present?
36
+ Stealth::Logger.l(topic: "smooch", message: "Message #{response.message._id} successfully sent.")
37
+ end
38
+ end
39
+
40
+ def self.generate_jwt_token
41
+ payload = { scope: 'app' }
42
+ jwtHeader = { kid: Stealth.config.smooch.key_id }
43
+ token = JWT.encode(payload, Stealth.config.smooch.secret, 'HS256', jwtHeader)
44
+
45
+ puts "#{Stealth::Logger.colorize('[JWT Token]', color: :green)} Your Smooch token is below. Please set the value `jwt_token` to the token in services.yml."
46
+ puts token
47
+ end
48
+
49
+ def self.register_webhooks(endpoint:)
50
+ smooch_webhook_api = SmoochApi::WebhookApi.new
51
+ webhook_create_body = SmoochApi::WebhookCreate.new(
52
+ target: endpoint,
53
+ triggers: ['message:appUser', 'postback']
54
+ )
55
+
56
+ response = smooch_webhook_api.create_webhook(
57
+ Stealth.config.smooch.app_id,
58
+ webhook_create_body
59
+ )
60
+
61
+ puts "#{Stealth::Logger.colorize('[Web Hooks]', color: :green)} Your Smooch webhooks have been registered to: #{endpoint}"
62
+ end
63
+
64
+ def self.set_persistent_menu(menu)
65
+ smooch_api = SmoochApi::IntegrationApi.new
66
+ response = smooch_api.list_integrations(Stealth.config.smooch.app_id)
67
+ response.integrations.each do |integration|
68
+ begin
69
+ smooch_api.update_integration_menu(Stealth.config.smooch.app_id, integration._id, menu)
70
+ puts "#{Stealth::Logger.colorize('[Persistent Menu]', color: :green)} set for #{integration.type} integration."
71
+ rescue SmoochApi::ApiError
72
+ # Not all integrations support the persistent menu
73
+ puts "#{Stealth::Logger.colorize('[Persistent Menu]', color: :red)} Skipping #{integration.type} integration. Persistent Menu is not supported."
74
+ next
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,56 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Stealth
5
+ module Services
6
+ module Smooch
7
+
8
+ class MessageEvent
9
+
10
+ attr_reader :service_message, :params
11
+
12
+ def initialize(service_message:, params:)
13
+ @service_message = service_message
14
+ @params = params
15
+ end
16
+
17
+ def process
18
+ fetch_message
19
+ fetch_location
20
+ fetch_attachments
21
+ end
22
+
23
+ private
24
+
25
+ def fetch_message
26
+ service_message.message = params['text']
27
+ end
28
+
29
+ def fetch_location
30
+ if params['type'] == 'location'
31
+ service_message.location = {
32
+ lat: params['coordinates']['lat'],
33
+ lng: params['coordinates']['lng']
34
+ }
35
+ end
36
+ end
37
+
38
+ def fetch_attachments
39
+ if params['type'] == 'image'
40
+ service_message.attachments << {
41
+ type: 'image',
42
+ url: params['mediaUrl']
43
+ }
44
+ elsif params['type'] == 'file'
45
+ service_message.attachments << {
46
+ type: params['mediaType'],
47
+ url: params['mediaUrl']
48
+ }
49
+ end
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Stealth
5
+ module Services
6
+ module Smooch
7
+
8
+ class PostbackEvent
9
+
10
+ attr_reader :service_message, :params
11
+
12
+ def initialize(service_message:, params:)
13
+ @service_message = service_message
14
+ @params = params
15
+ end
16
+
17
+ def process
18
+ fetch_payload
19
+ end
20
+
21
+ private
22
+
23
+ def fetch_payload
24
+ service_message.payload = params['action']['payload']
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,71 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'stealth/services/smooch/events/message_event'
5
+ require 'stealth/services/smooch/events/postback_event'
6
+
7
+ module Stealth
8
+ module Services
9
+ module Smooch
10
+
11
+ class MessageHandler < Stealth::Services::BaseMessageHandler
12
+
13
+ attr_reader :service_message, :params, :headers,
14
+ :smooch_response, :smooch_message
15
+
16
+ def initialize(params:, headers:)
17
+ @params = params
18
+ @headers = headers
19
+ end
20
+
21
+ def coordinate
22
+ # Queue the request processing so we can respond quickly to Smooch
23
+ # and also keep track of this message
24
+ Stealth::Services::HandleMessageJob.perform_async('smooch', params, {})
25
+
26
+ # Relay our acceptance
27
+ [200, 'OK']
28
+ end
29
+
30
+ def process
31
+ @service_message = ServiceMessage.new(service: 'smooch')
32
+ @smooch_response = params
33
+ @smooch_message = @smooch_response['messages'].first
34
+ service_message.sender_id = get_sender_id
35
+ service_message.timestamp = get_timestamp
36
+
37
+ process_smooch_event
38
+
39
+ service_message
40
+ end
41
+
42
+ private
43
+
44
+ def get_sender_id
45
+ smooch_response['appUser']['_id']
46
+ end
47
+
48
+ def get_timestamp
49
+ Time.at(smooch_message['received']).to_datetime
50
+ end
51
+
52
+ def process_smooch_event
53
+ if smooch_response['trigger'] == 'message:appUser'
54
+ message_event = Stealth::Services::Smooch::MessageEvent.new(
55
+ service_message: service_message,
56
+ params: smooch_message
57
+ )
58
+ elsif smooch_response['trigger'] == 'postback'
59
+ message_event = Stealth::Services::Smooch::PostbackEvent.new(
60
+ service_message: service_message,
61
+ params: smooch_response['postbacks'].first
62
+ )
63
+ end
64
+
65
+ message_event.process
66
+ end
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,319 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Stealth
5
+ module Services
6
+ module Smooch
7
+
8
+ class ReplyHandler < Stealth::Services::BaseReplyHandler
9
+
10
+ attr_reader :recipient_id, :reply
11
+
12
+ def initialize(recipient_id: nil, reply: nil)
13
+ @recipient_id = recipient_id
14
+ @reply = reply
15
+ end
16
+
17
+ def text
18
+ message = SmoochApi::MessagePost.new(
19
+ role: 'appMaker',
20
+ type: 'text',
21
+ text: reply['text']
22
+ )
23
+
24
+ if reply['suggestions'].present?
25
+ smooch_suggestions = generate_suggestions(suggestions: reply['suggestions'])
26
+ message.actions = smooch_suggestions
27
+ end
28
+
29
+ if reply['buttons'].present?
30
+ smooch_buttons = generate_buttons(buttons: reply['buttons'])
31
+ message.actions = smooch_buttons
32
+ end
33
+
34
+ message_template(action: 'post_message', message: message)
35
+ end
36
+
37
+ def image
38
+ check_if_arguments_are_valid!(
39
+ suggestions: reply['suggestions'],
40
+ buttons: reply['buttons']
41
+ )
42
+
43
+ message = SmoochApi::MessagePost.new(
44
+ role: 'appMaker',
45
+ type: 'image',
46
+ mediaUrl: reply['image_url']
47
+ )
48
+
49
+ if reply['suggestions'].present?
50
+ smooch_suggestions = generate_suggestions(suggestions: reply['suggestions'])
51
+ message.actions = smooch_suggestions
52
+ end
53
+
54
+ message_template(action: 'post_message', message: message)
55
+ end
56
+
57
+ def audio
58
+ check_if_arguments_are_valid!(
59
+ suggestions: reply['suggestions'],
60
+ buttons: reply['buttons']
61
+ )
62
+
63
+ message = SmoochApi::MessagePost.new(
64
+ role: 'appMaker',
65
+ type: 'file',
66
+ text: reply['text'],
67
+ mediaUrl: reply['audio_url']
68
+ )
69
+
70
+ if reply['suggestions'].present?
71
+ smooch_suggestions = generate_suggestions(suggestions: reply['suggestions'])
72
+ message.actions = smooch_suggestions
73
+ end
74
+
75
+ message_template(action: 'post_message', message: message)
76
+ end
77
+
78
+ def video
79
+ check_if_arguments_are_valid!(
80
+ suggestions: reply['suggestions'],
81
+ buttons: reply['buttons']
82
+ )
83
+
84
+ message = SmoochApi::MessagePost.new(
85
+ role: 'appMaker',
86
+ type: 'file',
87
+ text: reply['text'],
88
+ mediaUrl: reply['video_url']
89
+ )
90
+
91
+ if reply['suggestions'].present?
92
+ smooch_suggestions = generate_suggestions(suggestions: reply['suggestions'])
93
+ message.actions = smooch_suggestions
94
+ end
95
+
96
+ message_template(action: 'post_message', message: message)
97
+ end
98
+
99
+ def file
100
+ check_if_arguments_are_valid!(
101
+ suggestions: reply['suggestions'],
102
+ buttons: reply['buttons']
103
+ )
104
+
105
+ message = SmoochApi::MessagePost.new(
106
+ role: 'appMaker',
107
+ type: 'file',
108
+ text: reply['text'],
109
+ mediaUrl: reply['file_url']
110
+ )
111
+
112
+ if reply['suggestions'].present?
113
+ smooch_suggestions = generate_suggestions(suggestions: reply['suggestions'])
114
+ message.actions = smooch_suggestions
115
+ end
116
+
117
+ message_template(action: 'post_message', message: message)
118
+ end
119
+
120
+ def cards
121
+ message = SmoochApi::MessagePost.new(
122
+ role: 'appMaker',
123
+ type: 'carousel'
124
+ )
125
+
126
+ smooch_items = generate_card_items(elements: reply["elements"])
127
+ message.items = smooch_items
128
+
129
+ message_template(action: 'post_message', message: message)
130
+ end
131
+
132
+ def list
133
+ message = SmoochApi::MessagePost.new(
134
+ role: 'appMaker',
135
+ type: 'list'
136
+ )
137
+
138
+ smooch_items = generate_list_items(elements: reply["elements"])
139
+ message.items = smooch_items
140
+
141
+ if reply['buttons'].present?
142
+ if reply["buttons"].size > 1
143
+ raise(ArgumentError, "Smooch lists support a single button attached to the list itsef.")
144
+ end
145
+
146
+ smooch_buttons = generate_buttons(buttons: reply['buttons'])
147
+ message.actions = smooch_buttons
148
+ end
149
+
150
+ message_template(action: 'post_message', message: message)
151
+ end
152
+
153
+ def enable_typing_indicator
154
+ message = SmoochApi::TypingActivityTrigger.new(
155
+ role: 'appMaker',
156
+ type: 'typing:start'
157
+ )
158
+
159
+ message_template(action: 'trigger_typing_activity', message: message)
160
+ end
161
+
162
+ def disable_typing_indicator
163
+ message = SmoochApi::TypingActivityTrigger.new(
164
+ role: 'appMaker',
165
+ type: 'typing:stop'
166
+ )
167
+
168
+ message_template(action: 'trigger_typing_activity', message: message)
169
+ end
170
+
171
+ def delay
172
+ enable_typing_indicator
173
+ end
174
+
175
+ def persistent_menu
176
+ smooch_menu = SmoochApi::Menu.new
177
+
178
+ smooch_menu_items = generate_buttons(buttons: Stealth.config.smooch.setup.persistent_menu)
179
+ smooch_menu.items = smooch_menu_items
180
+
181
+ smooch_menu
182
+ end
183
+
184
+ private
185
+
186
+ def message_template(action:, message:)
187
+ {
188
+ recipient_id: recipient_id,
189
+ reply_type: action,
190
+ message: message
191
+ }
192
+ end
193
+
194
+ def generate_card_items(elements:)
195
+ if elements.size > 10
196
+ raise(ArgumentError, "Smooch cards can have at most 10 cards.")
197
+ end
198
+
199
+ smooch_items = elements.collect do |element|
200
+ smooch_item = item_template(element_type: 'card', element: element)
201
+ end
202
+
203
+ smooch_items
204
+ end
205
+
206
+ def generate_list_items(elements:)
207
+ if elements.size < 2 || elements.size > 4
208
+ raise(ArgumentError, "Smooch lists must have 2-4 elements.")
209
+ end
210
+
211
+ smooch_items = elements.collect do |element|
212
+ smooch_item = item_template(element_type: 'list', element: element)
213
+ end
214
+
215
+ smooch_items
216
+ end
217
+
218
+ def item_template(element_type:, element:)
219
+ unless element["title"].present?
220
+ raise(ArgumentError, "Smooch card and list elements must have a 'title' attribute.")
221
+ end
222
+
223
+ smooch_item = SmoochApi::MessageItem.new
224
+
225
+ smooch_item.title = element['title']
226
+
227
+ if element["subtitle"].present?
228
+ smooch_item.description = element["subtitle"]
229
+ end
230
+
231
+ if element["image_url"].present?
232
+ smooch_item.media_url = element["image_url"]
233
+ end
234
+
235
+ if element["buttons"].present?
236
+ if element_type == 'card' && element["buttons"].size > 3
237
+ raise(ArgumentError, "Smooch card elements only support 3 buttons.")
238
+ end
239
+
240
+ if element_type == 'list' && element["buttons"].size > 1
241
+ raise(ArgumentError, "Smooch list elements only support 1 button.")
242
+ end
243
+
244
+ smooch_buttons = generate_buttons(buttons: element['buttons'])
245
+ smooch_item.actions = smooch_buttons
246
+ end
247
+
248
+ smooch_item
249
+ end
250
+
251
+ def generate_suggestions(suggestions:)
252
+ quick_replies = suggestions.collect do |suggestion|
253
+ quick_reply = SmoochApi::Action.new(type: 'reply')
254
+
255
+ case suggestion["type"]
256
+ when 'location'
257
+ quick_reply.type = 'locationRequest'
258
+ quick_reply.text = suggestion["text"]
259
+ when 'phone'
260
+ quick_reply.text = suggestion["text"]
261
+ when 'email'
262
+ quick_reply.text = suggestion["text"]
263
+ else
264
+ quick_reply.text = suggestion["text"]
265
+
266
+ if suggestion["payload"].present?
267
+ quick_reply.payload = suggestion["payload"]
268
+ else
269
+ quick_reply.payload = suggestion["text"]
270
+ end
271
+
272
+ if suggestion["image_url"].present?
273
+ quick_reply.icon_url = suggestion["image_url"]
274
+ end
275
+ end
276
+
277
+ quick_reply
278
+ end
279
+
280
+ quick_replies
281
+ end
282
+
283
+ def generate_buttons(buttons:)
284
+ smooch_buttons = buttons.collect do |button|
285
+ case button['type']
286
+ when 'url'
287
+ smooch_button = SmoochApi::Action.new(type: 'webview')
288
+ smooch_button.uri = smooch_button.fallback = button["url"]
289
+ smooch_button.text = smooch_button.fallback = button["text"]
290
+
291
+ if button["webview_height"].present?
292
+ smooch_button.size = button["webview_height"]
293
+ end
294
+
295
+ when 'payload'
296
+ smooch_button = SmoochApi::Action.new(type: 'postback')
297
+ smooch_button.payload = button["payload"]
298
+ smooch_button.text = button["text"]
299
+
300
+ else
301
+ raise(Stealth::Errors::ServiceImpaired, "Sorry, we don't yet support #{button["type"]} buttons yet!")
302
+ end
303
+
304
+ smooch_button
305
+ end
306
+
307
+ smooch_buttons
308
+ end
309
+
310
+ def check_if_arguments_are_valid!(suggestions:, buttons:)
311
+ if suggestions.present? && buttons.present?
312
+ raise(ArgumentError, "A reply cannot have buttons and suggestions!")
313
+ end
314
+ end
315
+ end
316
+
317
+ end
318
+ end
319
+ end
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'stealth/services/smooch/client'
5
+
6
+ module Stealth
7
+ module Services
8
+ module Smooch
9
+
10
+ class Setup
11
+
12
+ class << self
13
+ def trigger
14
+ SmoochApi.configure do |config|
15
+ config.api_key['Authorization'] = Stealth.config.smooch.jwt_token
16
+ config.api_key_prefix['Authorization'] = 'Bearer'
17
+ end
18
+
19
+ reply_handler = Stealth::Services::Smooch::ReplyHandler.new
20
+ menu = reply_handler.persistent_menu
21
+ Stealth::Services::Smooch::Client.set_persistent_menu(menu)
22
+
23
+ if ENV['SMOOCH_ENDPOINT'].present?
24
+ Stealth::Services::Smooch::Client.register_webhooks(endpoint: ENV['SMOOCH_ENDPOINT'])
25
+ else
26
+ puts '[ERROR] Please set SMOOCH_ENDPOINT to the endpoint that will receive Smooch webhooks.'
27
+ end
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,16 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Stealth
5
+ module Services
6
+ module Smooch
7
+ module Version
8
+ def self.version
9
+ File.read(File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'VERSION')).strip
10
+ end
11
+ end
12
+
13
+ VERSION = Version.version
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,2 @@
1
+ require 'stealth/services/smooch/version'
2
+ require 'stealth/services/smooch/client'
@@ -0,0 +1,14 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+
5
+ require 'stealth'
6
+ require 'stealth-smooch'
7
+
8
+ # Requires supporting files with custom matchers and macros, etc,
9
+ # in ./support/ and its subdirectories.
10
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
11
+
12
+ RSpec.configure do |config|
13
+
14
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
4
+
5
+ describe "Stealth::Services::Smooch::Version" do
6
+
7
+ let(:version_in_file) { File.read(File.join(File.dirname(__FILE__), '..', 'VERSION')).strip }
8
+
9
+ it "should return the current gem version" do
10
+ expect(Stealth::Services::Smooch::Version.version).to eq version_in_file
11
+ end
12
+
13
+ it "should return the current gem version via a constant" do
14
+ expect(Stealth::Services::Smooch::VERSION).to eq version_in_file
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
2
+
3
+ version = File.read(File.join(File.dirname(__FILE__), 'VERSION')).strip
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'stealth-smooch'
7
+ s.summary = 'Stealth Smooch driver'
8
+ s.description = 'Smooch driver for Stealth.'
9
+ s.homepage = 'https://github.com/hellostealth/stealth-smooch'
10
+ s.licenses = ['MIT']
11
+ s.version = version
12
+ s.author = 'Mauricio Gomes'
13
+ s.email = 'mauricio@edge14.com'
14
+
15
+ s.add_dependency 'stealth', '< 2.0'
16
+ s.add_dependency 'jwt', '~> 2.1'
17
+ s.add_dependency 'smooch-api', '~> 4.0'
18
+
19
+ s.add_development_dependency 'rspec', '~> 3.6'
20
+ s.add_development_dependency 'rspec_junit_formatter', '~> 0.3'
21
+ s.add_development_dependency 'rack-test', '~> 1.1'
22
+
23
+ s.files = `git ls-files`.split("\n")
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
26
+ s.require_paths = ['lib']
27
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stealth-smooch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Mauricio Gomes
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-11-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: stealth
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "<"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "<"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: jwt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: smooch-api
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.6'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec_junit_formatter
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rack-test
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.1'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.1'
97
+ description: Smooch driver for Stealth.
98
+ email: mauricio@edge14.com
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - ".gitignore"
104
+ - Gemfile
105
+ - Gemfile.lock
106
+ - LICENSE
107
+ - README.md
108
+ - VERSION
109
+ - lib/stealth-smooch.rb
110
+ - lib/stealth/services/smooch/client.rb
111
+ - lib/stealth/services/smooch/events/message_event.rb
112
+ - lib/stealth/services/smooch/events/postback_event.rb
113
+ - lib/stealth/services/smooch/message_handler.rb
114
+ - lib/stealth/services/smooch/reply_handler.rb
115
+ - lib/stealth/services/smooch/setup.rb
116
+ - lib/stealth/services/smooch/version.rb
117
+ - lib/stealth/smooch.rb
118
+ - spec/spec_helper.rb
119
+ - spec/version_spec.rb
120
+ - stealth-smooch.gemspec
121
+ homepage: https://github.com/hellostealth/stealth-smooch
122
+ licenses:
123
+ - MIT
124
+ metadata: {}
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 2.7.7
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: Stealth Smooch driver
145
+ test_files:
146
+ - spec/spec_helper.rb
147
+ - spec/version_spec.rb