stealth-facebook 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4a38f865adee4d13ff7aafdfd61df335c1dc067b
4
- data.tar.gz: ca56525cba32394dc4aea0081ff5de0cba989ed3
3
+ metadata.gz: bb2add5ca870baf50a9dd8dd5e55ebe4625051f8
4
+ data.tar.gz: 0b880e4057e9c5445facfa83801d02c5788335fa
5
5
  SHA512:
6
- metadata.gz: 1c1a3de0d5e1bc33641420eead5f2f4ad09250b12a5192f38a72f0f17bb595091dfea03a58592c0fb10894a7f2bb9ca56129c2079a3b2b125cff5d5e15ca04fa
7
- data.tar.gz: b0fa545dba48b521e1ff6e7d17149f2445ed4e737a4f7ae9f9580417913e3cf9f62ba64af4f144b4439bb1fd039014774d1350cf8468411e2545bf68a9ee4f10
6
+ metadata.gz: 9ef0ef7851238ce6c4f6d0b17231695d9f146ceb172285dda30141c38875605f883d0c6d566bcb75b030dde6219d27de3b5861f1bb43e2e43c5ace53011ff9aa
7
+ data.tar.gz: 69cee3ec475fcaacda34d63b1732aaac5259f21d2a9d3f1ffe8b42b463b57fd16cc58fa623c1a903c5fdb2757c4d8be1812900da70c141f6d067bed7304b4901
@@ -0,0 +1,60 @@
1
+ # Ruby CircleCI 2.0 configuration file
2
+ #
3
+ # Check https://circleci.com/docs/2.0/language-ruby/ for more details
4
+ #
5
+ version: 2
6
+ jobs:
7
+ build:
8
+ docker:
9
+ # specify the version you desire here
10
+ - image: circleci/ruby:2.4.1-node-browsers
11
+ environment:
12
+ STEALTH_ENV: test
13
+
14
+ # Specify service dependencies here if necessary
15
+ # CircleCI maintains a library of pre-built images
16
+ # documented at https://circleci.com/docs/2.0/circleci-images/
17
+ # - image: circleci/postgres:9.4
18
+
19
+ working_directory: ~/repo
20
+
21
+ steps:
22
+ - checkout
23
+
24
+ # Download and cache dependencies
25
+ - restore_cache:
26
+ keys:
27
+ - v1-dependencies-{{ checksum "Gemfile.lock" }}
28
+ # fallback to using the latest cache if no exact match is found
29
+ - v1-dependencies-
30
+
31
+ - run:
32
+ name: install dependencies
33
+ command: |
34
+ bundle install --jobs=4 --retry=3 --path vendor/bundle
35
+
36
+ - save_cache:
37
+ paths:
38
+ - ./vendor/bundle
39
+ key: v1-dependencies-{{ checksum "Gemfile.lock" }}
40
+
41
+ # run tests!
42
+ - run:
43
+ name: run tests
44
+ command: |
45
+ mkdir /tmp/test-results
46
+ TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"
47
+
48
+ bundle exec rspec --format progress \
49
+ --format RspecJunitFormatter \
50
+ --out /tmp/test-results/rspec.xml \
51
+ --format progress \
52
+ -- \
53
+ $TEST_FILES
54
+
55
+ # collect reports
56
+ - store_test_results:
57
+ path: /tmp/test-results
58
+ - store_artifacts:
59
+ path: /tmp/test-results
60
+ destination: test-results
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,83 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ stealth-facebook (0.9.0)
5
+ faraday (~> 0.13)
6
+ stealth (~> 0.9)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport (5.1.4)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (~> 0.7)
14
+ minitest (~> 5.1)
15
+ tzinfo (~> 1.1)
16
+ concurrent-ruby (1.0.5)
17
+ connection_pool (2.2.1)
18
+ diff-lcs (1.3)
19
+ faraday (0.13.1)
20
+ multipart-post (>= 1.2, < 3)
21
+ i18n (0.9.1)
22
+ concurrent-ruby (~> 1.0)
23
+ minitest (5.10.3)
24
+ multi_json (1.12.2)
25
+ multipart-post (2.0.0)
26
+ mustermann (1.0.1)
27
+ puma (3.10.0)
28
+ rack (2.0.3)
29
+ rack-protection (2.0.0)
30
+ rack
31
+ rack-test (0.7.0)
32
+ rack (>= 1.0, < 3)
33
+ redis (4.0.1)
34
+ rspec (3.7.0)
35
+ rspec-core (~> 3.7.0)
36
+ rspec-expectations (~> 3.7.0)
37
+ rspec-mocks (~> 3.7.0)
38
+ rspec-core (3.7.0)
39
+ rspec-support (~> 3.7.0)
40
+ rspec-expectations (3.7.0)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.7.0)
43
+ rspec-mocks (3.7.0)
44
+ diff-lcs (>= 1.2.0, < 2.0)
45
+ rspec-support (~> 3.7.0)
46
+ rspec-support (3.7.0)
47
+ rspec_junit_formatter (0.3.0)
48
+ rspec-core (>= 2, < 4, != 2.12.0)
49
+ sidekiq (5.0.5)
50
+ concurrent-ruby (~> 1.0)
51
+ connection_pool (~> 2.2, >= 2.2.0)
52
+ rack-protection (>= 1.5.0)
53
+ redis (>= 3.3.4, < 5)
54
+ sinatra (2.0.0)
55
+ mustermann (~> 1.0)
56
+ rack (~> 2.0)
57
+ rack-protection (= 2.0.0)
58
+ tilt (~> 2.0)
59
+ stealth (0.9.5)
60
+ activesupport (~> 5.1)
61
+ faraday (~> 0.13)
62
+ multi_json (~> 1.12)
63
+ puma (~> 3.10)
64
+ sidekiq (~> 5.0)
65
+ sinatra (~> 2.0)
66
+ thor (~> 0.20)
67
+ thor (0.20.0)
68
+ thread_safe (0.3.6)
69
+ tilt (2.0.8)
70
+ tzinfo (1.2.4)
71
+ thread_safe (~> 0.1)
72
+
73
+ PLATFORMS
74
+ ruby
75
+
76
+ DEPENDENCIES
77
+ rack-test (~> 0.7)
78
+ rspec (~> 3.6)
79
+ rspec_junit_formatter (~> 0.3)
80
+ stealth-facebook!
81
+
82
+ BUNDLED WITH
83
+ 1.15.3
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2017 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 @@
1
+ # Stealth Facebook
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.9.1
@@ -0,0 +1 @@
1
+ require 'stealth/facebook'
@@ -0,0 +1,2 @@
1
+ require 'stealth/services/facebook/version'
2
+ require 'stealth/services/facebook/client'
@@ -0,0 +1,60 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'faraday'
5
+
6
+ require 'stealth/services/facebook/message_handler'
7
+ require 'stealth/services/facebook/reply_handler'
8
+ require 'stealth/services/facebook/setup'
9
+
10
+ module Stealth
11
+ module Services
12
+ module Facebook
13
+
14
+ class Client < Stealth::Services::BaseClient
15
+ FB_ENDPOINT = "https://graph.facebook.com/v2.10/me"
16
+
17
+ attr_reader :api_endpoint, :reply
18
+
19
+ def initialize(reply:, endpoint: 'messages')
20
+ @reply = reply
21
+ access_token = "access_token=#{Stealth.config.facebook.page_access_token}"
22
+ @api_endpoint = [[FB_ENDPOINT, endpoint].join('/'), access_token].join('?')
23
+ end
24
+
25
+ def transmit
26
+ headers = { "Content-Type" => "application/json" }
27
+ response = Faraday.post(api_endpoint, reply.to_json, headers)
28
+ Stealth::Logger.l(topic: "facebook", message: "Transmitting. Response: #{response.status}: #{response.body}")
29
+ end
30
+
31
+ def self.fetch_profile(recipient_id:, fields: nil)
32
+ if fields.blank?
33
+ fields = [:first_name, :last_name, :profile_pic, :locale, :timezone, :gender, :is_payment_enabled, :last_ad_referral]
34
+ end
35
+
36
+ query_hash ={
37
+ fields: fields.join(','),
38
+ access_token: Stealth.config.facebook.page_access_token
39
+ }
40
+
41
+ uri = URI::HTTPS.build(
42
+ host: "graph.facebook.com",
43
+ path: "/v2.10/#{recipient_id}",
44
+ query: query_hash.to_query
45
+ )
46
+
47
+ response = Faraday.get(uri.to_s)
48
+ Stealth::Logger.l(topic: "facebook", message: "Requested user profile for #{recipient_id}. Response: #{response.status}: #{response.body}")
49
+
50
+ if response.status.in?(200..299)
51
+ MultiJson.load(response.body)
52
+ else
53
+ false
54
+ end
55
+ end
56
+ end
57
+
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,59 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Stealth
5
+ module Services
6
+ module Facebook
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
+ if params['message']['quick_reply'].present?
27
+ service_message.message = params['message']['quick_reply']['payload']
28
+ elsif params['message']['text'].present?
29
+ service_message.message = params['message']['text']
30
+ end
31
+ end
32
+
33
+ def fetch_location
34
+ if params['location'].present?
35
+ lat = params['location']['coordinates']['lat']
36
+ lng = params['location']['coordinates']['long']
37
+ service_message.location = {
38
+ lat: lat,
39
+ lng: lng
40
+ }
41
+ end
42
+ end
43
+
44
+ def fetch_attachments
45
+ if params['attachments'].present? && params['attachments'].is_a?(Array)
46
+ params['attachments'].each do |attachment|
47
+ service_message.attachments << {
48
+ type: attachment['type'],
49
+ url: attachment['payload']['url']
50
+ }
51
+ end
52
+ end
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Stealth
5
+ module Services
6
+ module Facebook
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
+ fetch_referral
20
+ end
21
+
22
+ private
23
+
24
+ def fetch_payload
25
+ service_message.payload = params['postback']['payload']
26
+ end
27
+
28
+ def fetch_referral
29
+ service_message.referral = params['postback']['referral']
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,84 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'stealth/services/facebook/events/message_event'
5
+ require 'stealth/services/facebook/events/postback_event'
6
+
7
+ module Stealth
8
+ module Services
9
+ module Facebook
10
+
11
+ class MessageHandler < Stealth::Services::BaseMessageHandler
12
+
13
+ attr_reader :service_message, :params, :headers, :facebook_message
14
+
15
+ def initialize(params:, headers:)
16
+ @params = params
17
+ @headers = headers
18
+ end
19
+
20
+ def coordinate
21
+ if facebook_is_validating_webhook?
22
+ respond_with_validation
23
+ else
24
+ # Queue the request processing so we can respond quickly to FB
25
+ # and also keep track of this message
26
+ Stealth::Services::HandleMessageJob.perform_async('facebook', params, {})
27
+
28
+ # Relay our acceptance
29
+ [200, 'OK']
30
+ end
31
+ end
32
+
33
+ def process
34
+ @service_message = ServiceMessage.new(service: 'facebook')
35
+ @facebook_message = params['entry'].first['messaging'].first
36
+ service_message.sender_id = get_sender_id
37
+ service_message.timestamp = get_timestamp
38
+ process_facebook_event
39
+
40
+ service_message
41
+ end
42
+
43
+ private
44
+
45
+ def facebook_is_validating_webhook?
46
+ params['hub.verify_token'].present?
47
+ end
48
+
49
+ def respond_with_validation
50
+ if params['hub.verify_token'] == Stealth.config.facebook.verify_token
51
+ [200, params['hub.challenge']]
52
+ else
53
+ [401, "Verify token did not match environment variable."]
54
+ end
55
+ end
56
+
57
+ def get_sender_id
58
+ facebook_message['sender']['id']
59
+ end
60
+
61
+ def get_timestamp
62
+ Time.at(facebook_message['timestamp']).to_datetime
63
+ end
64
+
65
+ def process_facebook_event
66
+ if facebook_message['message'].present?
67
+ message_event = Stealth::Services::Facebook::MessageEvent.new(
68
+ service_message: service_message,
69
+ params: facebook_message
70
+ )
71
+ elsif facebook_message['postback'].present?
72
+ message_event = Stealth::Services::Facebook::PostbackEvent.new(
73
+ service_message: service_message,
74
+ params: facebook_message
75
+ )
76
+ end
77
+
78
+ message_event.process
79
+ end
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,480 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Stealth
5
+ module Services
6
+ module Facebook
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
+ check_if_arguments_are_valid!(
19
+ suggestions: reply['suggestions'],
20
+ buttons: reply['buttons']
21
+ )
22
+
23
+ template = unstructured_template
24
+ template['message']['text'] = reply['text']
25
+
26
+ if reply['suggestions'].present?
27
+ fb_suggestions = generate_suggestions(suggestions: reply['suggestions'])
28
+ template["message"]["quick_replies"] = fb_suggestions
29
+ end
30
+
31
+ # If buttons are present, we need to convert this to a button template
32
+ if reply['buttons'].present?
33
+ template['message'].delete('text')
34
+
35
+ fb_buttons = generate_buttons(buttons: reply['buttons'])
36
+ attachment = button_attachment_template(text: reply['text'], buttons: fb_buttons)
37
+ template['message']['attachment'] = attachment
38
+ end
39
+
40
+ template
41
+ end
42
+
43
+ def image
44
+ check_if_arguments_are_valid!(
45
+ suggestions: reply['suggestions'],
46
+ buttons: reply['buttons']
47
+ )
48
+
49
+ template = unstructured_template
50
+ attachment = attachment_template(
51
+ attachment_type: 'image',
52
+ attachment_url: reply['image_url']
53
+ )
54
+ template['message']['attachment'] = attachment
55
+
56
+ if reply['suggestions'].present?
57
+ fb_suggestions = generate_suggestions(suggestions: reply['suggestions'])
58
+ template["message"]["quick_replies"] = fb_suggestions
59
+ end
60
+
61
+ template
62
+ end
63
+
64
+ def audio
65
+ check_if_arguments_are_valid!(
66
+ suggestions: reply['suggestions'],
67
+ buttons: reply['buttons']
68
+ )
69
+
70
+ template = unstructured_template
71
+ attachment = attachment_template(
72
+ attachment_type: 'audio',
73
+ attachment_url: reply['audio_url']
74
+ )
75
+ template['message']['attachment'] = attachment
76
+
77
+ if reply['suggestions'].present?
78
+ fb_suggestions = generate_suggestions(suggestions: reply['suggestions'])
79
+ template["message"]["quick_replies"] = fb_suggestions
80
+ end
81
+
82
+ template
83
+ end
84
+
85
+ def video
86
+ check_if_arguments_are_valid!(
87
+ suggestions: reply['suggestions'],
88
+ buttons: reply['buttons']
89
+ )
90
+
91
+ template = unstructured_template
92
+ attachment = attachment_template(
93
+ attachment_type: 'video',
94
+ attachment_url: reply['video_url']
95
+ )
96
+ template['message']['attachment'] = attachment
97
+
98
+ if reply['suggestions'].present?
99
+ fb_suggestions = generate_suggestions(suggestions: reply['suggestions'])
100
+ template["message"]["quick_replies"] = fb_suggestions
101
+ end
102
+
103
+ template
104
+ end
105
+
106
+ def file
107
+ check_if_arguments_are_valid!(
108
+ suggestions: reply['suggestions'],
109
+ buttons: reply['buttons']
110
+ )
111
+
112
+ template = unstructured_template
113
+ attachment = attachment_template(
114
+ attachment_type: 'file',
115
+ attachment_url: reply['file_url']
116
+ )
117
+ template['message']['attachment'] = attachment
118
+
119
+ if reply['suggestions'].present?
120
+ fb_suggestions = generate_suggestions(suggestions: reply['suggestions'])
121
+ template["message"]["quick_replies"] = fb_suggestions
122
+ end
123
+
124
+ template
125
+ end
126
+
127
+ def cards
128
+ template = card_template(
129
+ sharable: reply["sharable"],
130
+ aspect_ratio: reply["aspect_ratio"]
131
+ )
132
+
133
+ fb_elements = generate_card_elements(elements: reply["elements"])
134
+ template["message"]["attachment"]["payload"]["elements"] = fb_elements
135
+
136
+ if reply['suggestions'].present?
137
+ fb_suggestions = generate_suggestions(suggestions: reply['suggestions'])
138
+ template["message"]["quick_replies"] = fb_suggestions
139
+ end
140
+
141
+ template
142
+ end
143
+
144
+ def list
145
+ template = list_template(
146
+ top_element_style: reply["top_element_style"]
147
+ )
148
+
149
+ fb_elements = generate_list_elements(elements: reply["elements"])
150
+ template["message"]["attachments"]["payload"]["elements"] = fb_elements
151
+
152
+ if reply["buttons"].present?
153
+ if reply["buttons"].size > 1
154
+ raise(ArgumentError, "Facebook lists support a single button attached to the list itsef.")
155
+ end
156
+
157
+ template["message"]["attachments"]["payload"]["buttons"] = generate_buttons(buttons: reply["buttons"])
158
+ end
159
+
160
+ template
161
+ end
162
+
163
+ def mark_seen
164
+ sender_action_template(action: 'mark_seen')
165
+ end
166
+
167
+ def enable_typing_indicator
168
+ sender_action_template(action: 'typing_on')
169
+ end
170
+
171
+ def disable_typing_indicator
172
+ sender_action_template(action: 'typing_off')
173
+ end
174
+
175
+ def delay
176
+ enable_typing_indicator
177
+ end
178
+
179
+ # generates property/value pairs required to set the profile
180
+ def messenger_profile
181
+ unless Stealth.config.facebook.setup.present?
182
+ raise Stealth::Errors::ConfigurationError, "Setup for Facebook is not specified in services.yml."
183
+ end
184
+
185
+ profile = {}
186
+ Stealth.config.facebook.setup.each do |profile_option, _|
187
+ profile[profile_option] = self.send(profile_option)
188
+ end
189
+
190
+ profile
191
+ end
192
+
193
+ private
194
+
195
+ def unstructured_template
196
+ {
197
+ "recipient" => {
198
+ "id" => recipient_id
199
+ },
200
+ "message" => { }
201
+ }
202
+ end
203
+
204
+ def card_template(sharable: nil, aspect_ratio: nil)
205
+ template = {
206
+ "recipient" => {
207
+ "id" => recipient_id
208
+ },
209
+ "message" => {
210
+ "attachment" => {
211
+ "type" => "template",
212
+ "payload" => {
213
+ "template_type" => "generic",
214
+ "elements" => []
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ if sharable.present?
221
+ template["message"]["payload"]["sharable"] = sharable
222
+ end
223
+
224
+ if aspect_ratio.present?
225
+ template["message"]["payload"]["image_aspect_ratio"] = aspect_ratio
226
+ end
227
+
228
+ template
229
+ end
230
+
231
+ def list_template(top_element_style: nil, buttons: [])
232
+ template = {
233
+ "recipient" => {
234
+ "id" => recipient_id
235
+ },
236
+ "message" => {
237
+ "attachment" => {
238
+ "type" => "template",
239
+ "payload" => {
240
+ "template_type" => "list",
241
+ "elements" => []
242
+ }
243
+ }
244
+ }
245
+ }
246
+
247
+ if top_element_style.present?
248
+ unless ['large', 'compact'].include?(top_element_style)
249
+ raise(ArgumentError, "Facebook list replies only support 'large' or 'compact' as the top_element_style.")
250
+ end
251
+
252
+ template["message"]["payload"]["top_element_style"] = top_element_style
253
+ end
254
+
255
+ if buttons.present?
256
+ unless buttons.size > 1
257
+ raise(ArgumentError, "Facebook lists only support a single button in the top element.")
258
+ end
259
+
260
+ template["message"]["payload"]["buttons"] = aspect_ratio
261
+ end
262
+
263
+ template
264
+ end
265
+
266
+ def element_template(element_type:, element:)
267
+ unless element["title"].present?
268
+ raise(ArgumentError, "Facebook card and list elements must have a 'title' attribute.")
269
+ end
270
+
271
+ template = {
272
+ "title" => element["title"]
273
+ }
274
+
275
+ if element["subtitle"].present?
276
+ template["subtitle"] = element["subtitle"]
277
+ end
278
+
279
+ if element["image_url"].present?
280
+ template["image_url"] = element["image_url"]
281
+ end
282
+
283
+ if element["default_action"].present?
284
+ default_action = generate_default_action(action_params: element["default_action"])
285
+ template["default_action"] = default_action
286
+ end
287
+
288
+ if element["buttons"].present?
289
+ if element_type == 'card' && element["buttons"].size > 3
290
+ raise(ArgumentError, "Facebook card elements only support 3 buttons.")
291
+ end
292
+
293
+ if element_type == 'list' && element["buttons"].size > 1
294
+ raise(ArgumentError, "Facebook list elements only support 1 button.")
295
+ end
296
+
297
+ fb_buttons = generate_buttons(buttons: element["buttons"])
298
+ template["buttons"] = fb_buttons
299
+ end
300
+
301
+ template
302
+ end
303
+
304
+ def attachment_template(attachment_type:, attachment_url:)
305
+ {
306
+ "type" => attachment_type,
307
+ "payload" => {
308
+ "url" => attachment_url
309
+ }
310
+ }
311
+ end
312
+
313
+ def button_attachment_template(text:, buttons:)
314
+ {
315
+ "type" => "template",
316
+ "payload" => {
317
+ "template_type" => "button",
318
+ "text" => text,
319
+ "buttons" => buttons
320
+ }
321
+ }
322
+ end
323
+
324
+ def sender_action_template(action:)
325
+ {
326
+ "recipient" => {
327
+ "id" => recipient_id
328
+ },
329
+ "sender_action" => action
330
+ }
331
+ end
332
+
333
+ def generate_card_elements(elements:)
334
+ if elements.size > 10
335
+ raise(ArgumentError, "Facebook cards can have at most 10 cards.")
336
+ end
337
+
338
+ fb_elements = elements.collect do |element|
339
+ element_template(element_type: 'card', element: element)
340
+ end
341
+
342
+ fb_elements
343
+ end
344
+
345
+ def generate_list_elements(elements:)
346
+ if elements.size < 2 || elements.size > 4
347
+ raise(ArgumentError, "Facebook lists must have 2-4 elements.")
348
+ end
349
+
350
+ fb_elements = elements.collect do |element|
351
+ element_template(element_type: 'list', element: element)
352
+ end
353
+
354
+ fb_elements
355
+ end
356
+
357
+ def generate_suggestions(suggestions:)
358
+ quick_replies = suggestions.collect do |suggestion|
359
+ # If the user selected a location-type button, no other info needed
360
+ if suggestion["type"] == 'location'
361
+ quick_reply = { "content_type" => "location" }
362
+
363
+ # Facebook only supports these two types for now
364
+ else
365
+ quick_reply = {
366
+ "content_type" => "text",
367
+ "title" => suggestion["text"]
368
+ }
369
+
370
+ if suggestion["payload"].present?
371
+ quick_reply["payload"] = suggestion["payload"]
372
+ else
373
+ quick_reply["payload"] = suggestion["text"]
374
+ end
375
+
376
+ if suggestion["image_url"].present?
377
+ quick_reply["image_url"] = suggestion["image_url"]
378
+ end
379
+ end
380
+
381
+ quick_reply
382
+ end
383
+
384
+ quick_replies
385
+ end
386
+
387
+ # Requires adding support for Buy, Log In, Log Out, and Share button types
388
+ def generate_buttons(buttons:)
389
+ fb_buttons = buttons.collect do |button|
390
+ case button['type']
391
+ when 'url'
392
+ button = {
393
+ "type" => "web_url",
394
+ "url" => button["url"],
395
+ "title" => button["text"]
396
+ }
397
+
398
+ if button["webview_height"].present?
399
+ button["webview_height_ratio"] = button["webview_height"]
400
+ end
401
+
402
+ button
403
+
404
+ when 'payload'
405
+ button = {
406
+ "type" => "postback",
407
+ "payload" => button["payload"],
408
+ "title" => button["text"]
409
+ }
410
+
411
+ when 'call'
412
+ button = {
413
+ "type" => "phone_number",
414
+ "payload" => button["phone_number"],
415
+ "title" => button["text"]
416
+ }
417
+
418
+ when 'nested'
419
+ button = {
420
+ "type" => "nested",
421
+ "title" => button["text"],
422
+ "call_to_actions" => generate_buttons(buttons: button["buttons"])
423
+ }
424
+
425
+ else
426
+ raise(Stealth::Errors::ServiceImpaired, "Sorry, we don't yet support #{button["type"]} buttons yet!")
427
+ end
428
+
429
+ button
430
+ end
431
+
432
+ fb_buttons
433
+ end
434
+
435
+ def generate_default_action(action_params:)
436
+ default_action = {
437
+ "type" => "web_url",
438
+ "url" => action_params["url"]
439
+ }
440
+
441
+ if action_params["webview_height"].present?
442
+ action_params["webview_height_ratio"] = action_params["webview_height"]
443
+ end
444
+
445
+ default_action
446
+ end
447
+
448
+ def check_if_arguments_are_valid!(suggestions:, buttons:)
449
+ if suggestions.present? && buttons.present?
450
+ raise(ArgumentError, "A reply cannot have buttons and suggestions!")
451
+ end
452
+ end
453
+
454
+ def greeting
455
+ Stealth.config.facebook.setup.greeting.map do |greeting|
456
+ {
457
+ "locale" => greeting["locale"],
458
+ "text" => greeting["text"]
459
+ }
460
+ end
461
+ end
462
+
463
+ def persistent_menu
464
+ Stealth.config.facebook.setup.persistent_menu.map do |persistent_menu|
465
+ {
466
+ "locale" => persistent_menu['locale'],
467
+ "composer_input_disabled" => (persistent_menu['composer_input_disabled'] || false),
468
+ "call_to_actions" => generate_buttons(buttons: persistent_menu['call_to_actions'])
469
+ }
470
+ end
471
+ end
472
+
473
+ def get_started
474
+ Stealth.config.facebook.setup.get_started
475
+ end
476
+ end
477
+
478
+ end
479
+ end
480
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'stealth/services/facebook/client'
5
+
6
+ module Stealth
7
+ module Services
8
+ module Facebook
9
+
10
+ class Setup
11
+
12
+ class << self
13
+ def trigger
14
+ reply_handler = Stealth::Services::Facebook::ReplyHandler.new
15
+ reply = reply_handler.messenger_profile
16
+ client = Stealth::Services::Facebook::Client.new(reply: reply, endpoint: 'messenger_profile')
17
+ client.transmit
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Stealth
5
+ module Services
6
+ module Facebook
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,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-facebook'
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::Facebook::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::Facebook::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::Facebook::VERSION).to eq version_in_file
15
+ end
16
+ end
@@ -0,0 +1,26 @@
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-facebook'
7
+ s.summary = 'Stealth Facebook Messenger driver'
8
+ s.description = 'Facebook Messenger driver for Stealth.'
9
+ s.homepage = 'https://github.com/whoisblackops/stealth-facebook'
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', '~> 0.9'
16
+ s.add_dependency 'faraday', '~> 0.13'
17
+
18
+ s.add_development_dependency 'rspec', '~> 3.6'
19
+ s.add_development_dependency 'rspec_junit_formatter', '~> 0.3'
20
+ s.add_development_dependency 'rack-test', '~> 0.7'
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
25
+ s.require_paths = ['lib']
26
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stealth-facebook
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mauricio Gomes
@@ -85,7 +85,26 @@ email: mauricio@edge14.com
85
85
  executables: []
86
86
  extensions: []
87
87
  extra_rdoc_files: []
88
- files: []
88
+ files:
89
+ - ".circleci/config.yml"
90
+ - ".gitignore"
91
+ - Gemfile
92
+ - Gemfile.lock
93
+ - LICENSE
94
+ - README.md
95
+ - VERSION
96
+ - lib/stealth-facebook.rb
97
+ - lib/stealth/facebook.rb
98
+ - lib/stealth/services/facebook/client.rb
99
+ - lib/stealth/services/facebook/events/message_event.rb
100
+ - lib/stealth/services/facebook/events/postback_event.rb
101
+ - lib/stealth/services/facebook/message_handler.rb
102
+ - lib/stealth/services/facebook/reply_handler.rb
103
+ - lib/stealth/services/facebook/setup.rb
104
+ - lib/stealth/services/facebook/version.rb
105
+ - spec/spec_helper.rb
106
+ - spec/version_spec.rb
107
+ - stealth-facebook.gemspec
89
108
  homepage: https://github.com/whoisblackops/stealth-facebook
90
109
  licenses:
91
110
  - MIT
@@ -110,4 +129,6 @@ rubygems_version: 2.6.12
110
129
  signing_key:
111
130
  specification_version: 4
112
131
  summary: Stealth Facebook Messenger driver
113
- test_files: []
132
+ test_files:
133
+ - spec/spec_helper.rb
134
+ - spec/version_spec.rb