xip-facebook 0.22.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.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.22.0
Binary file
@@ -0,0 +1 @@
1
+ require 'xip/facebook'
@@ -0,0 +1,2 @@
1
+ require 'xip/services/facebook/version'
2
+ require 'xip/services/facebook/client'
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'http'
4
+
5
+ require 'xip/services/facebook/message_handler'
6
+ require 'xip/services/facebook/reply_handler'
7
+ require 'xip/services/facebook/setup'
8
+
9
+ module Xip
10
+ module Services
11
+ module Facebook
12
+ class Client < Xip::Services::BaseClient
13
+
14
+ FB_ENDPOINT = if ENV['FACEBOOK_API_VERSION'].present?
15
+ "https://graph.facebook.com/v#{ENV['FACEBOOK_API_VERSION']}/me"
16
+ else
17
+ "https://graph.facebook.com/v3.2/me"
18
+ end
19
+
20
+ attr_reader :api_endpoint, :reply
21
+
22
+ def initialize(reply:, endpoint: 'messages')
23
+ @reply = reply
24
+ access_token = "access_token=#{Xip.config.facebook.page_access_token}"
25
+ @api_endpoint = [[FB_ENDPOINT, endpoint].join('/'), access_token].join('?')
26
+ end
27
+
28
+ def transmit
29
+ res = self
30
+ .class
31
+ .http_client
32
+ .post(api_endpoint, body: MultiJson.dump(reply))
33
+
34
+ if res.status.client_error? # HTTP 4xx error
35
+ # Messenger error sub-codes (https://developers.facebook.com/docs/messenger-platform/reference/send-api/error-codes)
36
+ case res.body
37
+ when /1545041/
38
+ raise Xip::Errors::UserOptOut
39
+ when /2018108/
40
+ raise Xip::Errors::UserOptOut
41
+ when /2018028/
42
+ raise Xip::Errors::InvalidSessionID.new('Cannot message users who are not admins, developers or testers of the app until pages_messaging permission is reviewed and the app is live.')
43
+ end
44
+ end
45
+
46
+ Xip::Logger.l(
47
+ topic: "facebook",
48
+ message: "Transmitted. Response: #{res.status.code}: #{res.body}"
49
+ )
50
+ end
51
+
52
+ def self.http_client
53
+ headers = {
54
+ 'Content-Type' => 'application/json'
55
+ }
56
+ HTTP.timeout(connect: 15, read: 60).headers(headers)
57
+ end
58
+
59
+ def self.fetch_profile(recipient_id:, fields: nil)
60
+ if fields.blank?
61
+ fields = [:id, :name, :first_name, :last_name, :profile_pic]
62
+ end
63
+
64
+ query_hash = {
65
+ fields: fields.join(','),
66
+ access_token: Xip.config.facebook.page_access_token
67
+ }
68
+
69
+ uri = URI::HTTPS.build(
70
+ host: "graph.facebook.com",
71
+ path: "/#{recipient_id}",
72
+ query: query_hash.to_query
73
+ )
74
+
75
+ res = http_client.get(uri.to_s)
76
+ Xip::Logger.l(topic:
77
+ 'facebook',
78
+ message: "Requested user profile for #{recipient_id}. Response: #{res.status.code}: #{res.body}"
79
+ )
80
+
81
+ if res.status.success?
82
+ MultiJson.load(res.body.to_s)
83
+ else
84
+ raise(
85
+ Xip::Errors::ServiceError,
86
+ "Facebook error #{res.status}: #{res.body}"
87
+ )
88
+ end
89
+ end
90
+
91
+ def self.track(recipient_id:, metric:, value:, options: {})
92
+ metric_values = [{
93
+ '_eventName' => metric,
94
+ '_valueToSum' => value
95
+ }]
96
+
97
+ metric_values.first.merge!(options)
98
+
99
+ params = {
100
+ event: 'CUSTOM_APP_EVENTS',
101
+ custom_events: MultiJson.dump(metric_values),
102
+ advertiser_tracking_enabled: 1,
103
+ application_tracking_enabled: 1,
104
+ extinfo: MultiJson.dump(['mb1']),
105
+ page_scoped_user_id: recipient_id,
106
+ page_id: Xip.config.facebook.page_id
107
+ }
108
+
109
+ uri = URI::HTTPS.build(
110
+ host: "graph.facebook.com",
111
+ path: "/#{Xip.config.facebook.app_id}/activities"
112
+ )
113
+
114
+ res = http_client.post(uri.to_s, body: MultiJson.dump(params))
115
+ Xip::Logger.l(
116
+ topic: "facebook",
117
+ message: "Sent custom event for metric: #{metric} and value: #{value}. Response: #{res.status}: #{res.body}"
118
+ )
119
+
120
+ if res.status.success?
121
+ MultiJson.load(res.body.to_s)
122
+ else
123
+ raise(
124
+ Xip::Errors::ServiceError,
125
+ "Facebook error #{res.status}: #{res.body}"
126
+ )
127
+ end
128
+ end
129
+
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xip
4
+ module Services
5
+ module Facebook
6
+
7
+ class MessageEvent
8
+
9
+ attr_reader :service_message, :params
10
+
11
+ def initialize(service_message:, params:)
12
+ @service_message = service_message
13
+ @params = params
14
+ end
15
+
16
+ def process
17
+ fetch_message
18
+ fetch_location
19
+ fetch_attachments
20
+ end
21
+
22
+ private
23
+
24
+ def fetch_message
25
+ if params.dig('message', 'quick_reply').present?
26
+ service_message.message = params.dig('message', 'text')
27
+ service_message.payload = params.dig('message', 'quick_reply', 'payload')
28
+ elsif params.dig('message', 'text').present?
29
+ service_message.message = params.dig('message', 'text')
30
+ end
31
+ end
32
+
33
+ def fetch_location
34
+ if params.dig('message', 'attachments').present? && params.dig('message', 'attachments').is_a?(Array)
35
+ params.dig('message', 'attachments').each do |attachment|
36
+ next unless attachment['type'] == 'location'
37
+
38
+ lat = attachment.dig('payload', 'coordinates', 'lat')
39
+ lng = attachment.dig('payload', 'coordinates', 'long')
40
+
41
+ service_message.location = {
42
+ lat: lat,
43
+ lng: lng
44
+ }
45
+ end
46
+ end
47
+ end
48
+
49
+ def fetch_attachments
50
+ if params.dig('message', 'attachments').present? && params.dig('message', 'attachments').is_a?(Array)
51
+ params.dig('message', 'attachments').each do |attachment|
52
+ # Seems to be a bug in Messenger, but in attachments of type `fallback`
53
+ # we are seeing the URL come in at the attachment-level rather than
54
+ # nested within the payload as the API specifies:
55
+ # https://developers.facebook.com/docs/messenger-platform/reference/webhook-events/messages
56
+ payload_url = if attachment.dig('payload', 'url').present?
57
+ attachment['payload']['url']
58
+ else
59
+ attachment['url']
60
+ end
61
+
62
+ service_message.attachments << {
63
+ type: attachment['type'],
64
+ url: payload_url
65
+ }
66
+ end
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xip
4
+ module Services
5
+ module Facebook
6
+
7
+ class MessageReadsEvent
8
+
9
+ attr_reader :service_message, :params
10
+
11
+ def initialize(service_message:, params:)
12
+ @service_message = service_message
13
+ @params = params
14
+ end
15
+
16
+ def process
17
+ fetch_read
18
+ end
19
+
20
+ private
21
+
22
+ def fetch_read
23
+ service_message.read = { watermark: get_timestamp, seq: params['read']['seq'] }
24
+ end
25
+
26
+ def get_timestamp
27
+ Time.at(params['read']['watermark']/1000).to_datetime
28
+ end
29
+ end
30
+
31
+ module ReadsEvent
32
+ attr_accessor :read
33
+ def initialize(service:)
34
+ @read = {}
35
+ super
36
+ end
37
+ end
38
+
39
+ end
40
+ end
41
+
42
+
43
+ class ServiceMessage
44
+ prepend Services::Facebook::ReadsEvent
45
+ end
46
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xip
4
+ module Services
5
+ module Facebook
6
+
7
+ class MessagingReferralEvent
8
+
9
+ attr_reader :service_message, :params
10
+
11
+ def initialize(service_message:, params:)
12
+ @service_message = service_message
13
+ @params = params
14
+ end
15
+
16
+ def process
17
+ fetch_referral
18
+ end
19
+
20
+ private
21
+
22
+ def fetch_referral
23
+ service_message.referral = params['referral']
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xip
4
+ module Services
5
+ module Facebook
6
+
7
+ class PostbackEvent
8
+
9
+ attr_reader :service_message, :params
10
+
11
+ def initialize(service_message:, params:)
12
+ @service_message = service_message
13
+ @params = params
14
+ end
15
+
16
+ def process
17
+ fetch_payload
18
+ fetch_referral
19
+ end
20
+
21
+ private
22
+
23
+ def fetch_payload
24
+ service_message.payload = params.dig('postback', 'payload')
25
+ end
26
+
27
+ def fetch_referral
28
+ service_message.referral = params.dig('postback', 'referral')
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'xip/services/facebook/events/message_event'
4
+ require 'xip/services/facebook/events/postback_event'
5
+ require 'xip/services/facebook/events/message_reads_event'
6
+ require 'xip/services/facebook/events/messaging_referral_event'
7
+
8
+ module Xip
9
+ module Services
10
+ module Facebook
11
+
12
+ class MessageHandler < Xip::Services::BaseMessageHandler
13
+
14
+ attr_reader :service_message, :params, :headers, :facebook_message
15
+
16
+ def initialize(params:, headers:)
17
+ @params = params
18
+ @headers = headers
19
+ end
20
+
21
+ def coordinate
22
+ if facebook_is_validating_webhook?
23
+ respond_with_validation
24
+ else
25
+ # Queue the request processing so we can respond quickly to FB
26
+ # and also keep track of this message
27
+ Xip::Services::HandleMessageJob.perform_async(
28
+ 'facebook',
29
+ params,
30
+ headers
31
+ )
32
+
33
+ # Relay our acceptance
34
+ [200, 'OK']
35
+ end
36
+ end
37
+
38
+ def process
39
+ @service_message = ServiceMessage.new(service: 'facebook')
40
+ @facebook_message = params['entry'].first['messaging'].first
41
+ service_message.sender_id = get_sender_id
42
+ service_message.target_id = get_target_id
43
+ service_message.timestamp = get_timestamp
44
+ process_facebook_event
45
+
46
+ service_message
47
+ end
48
+
49
+ private
50
+
51
+ def facebook_is_validating_webhook?
52
+ params['hub.verify_token'].present?
53
+ end
54
+
55
+ def respond_with_validation
56
+ if params['hub.verify_token'] == Xip.config.facebook.verify_token
57
+ [200, params['hub.challenge']]
58
+ else
59
+ [401, "Verify token did not match environment variable."]
60
+ end
61
+ end
62
+
63
+ def get_sender_id
64
+ facebook_message['sender']['id']
65
+ end
66
+
67
+ def get_target_id
68
+ facebook_message['recipient']['id']
69
+ end
70
+
71
+ def get_timestamp
72
+ Time.at(facebook_message['timestamp']/1000).to_datetime
73
+ end
74
+
75
+ def process_facebook_event
76
+ if facebook_message['message'].present?
77
+ message_event = Xip::Services::Facebook::MessageEvent.new(
78
+ service_message: service_message,
79
+ params: facebook_message
80
+ )
81
+ elsif facebook_message['postback'].present?
82
+ message_event = Xip::Services::Facebook::PostbackEvent.new(
83
+ service_message: service_message,
84
+ params: facebook_message
85
+ )
86
+ elsif facebook_message['read'].present?
87
+ message_event = Xip::Services::Facebook::MessageReadsEvent.new(
88
+ service_message: service_message,
89
+ params: facebook_message
90
+ )
91
+ elsif facebook_message['referral'].present?
92
+ message_event = Xip::Services::Facebook::MessagingReferralEvent.new(
93
+ service_message: service_message,
94
+ params: facebook_message
95
+ )
96
+ end
97
+
98
+ message_event.process
99
+ end
100
+ end
101
+
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,502 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xip
4
+ module Services
5
+ module Facebook
6
+
7
+ class ReplyHandler < Xip::Services::BaseReplyHandler
8
+
9
+ attr_reader :recipient_id, :reply
10
+
11
+ def initialize(recipient_id: nil, reply: nil)
12
+ @recipient_id = recipient_id
13
+ @reply = reply
14
+ end
15
+
16
+ def text
17
+ check_if_arguments_are_valid!(
18
+ suggestions: reply['suggestions'],
19
+ buttons: reply['buttons']
20
+ )
21
+
22
+ template = unstructured_template
23
+ template['message']['text'] = reply['text']
24
+
25
+ if reply['suggestions'].present?
26
+ fb_suggestions = generate_suggestions(suggestions: reply['suggestions'])
27
+ template["message"]["quick_replies"] = fb_suggestions
28
+ end
29
+
30
+ # If buttons are present, we need to convert this to a button template
31
+ if reply['buttons'].present?
32
+ template['message'].delete('text')
33
+
34
+ fb_buttons = generate_buttons(buttons: reply['buttons'])
35
+ attachment = button_attachment_template(text: reply['text'], buttons: fb_buttons)
36
+ template['message']['attachment'] = attachment
37
+ end
38
+
39
+ template
40
+ end
41
+
42
+ def image
43
+ check_if_arguments_are_valid!(
44
+ suggestions: reply['suggestions'],
45
+ buttons: reply['buttons']
46
+ )
47
+
48
+ template = unstructured_template
49
+ attachment = attachment_template(
50
+ attachment_type: 'image',
51
+ attachment_url: reply['image_url']
52
+ )
53
+ template['message']['attachment'] = attachment
54
+
55
+ if reply['suggestions'].present?
56
+ fb_suggestions = generate_suggestions(suggestions: reply['suggestions'])
57
+ template["message"]["quick_replies"] = fb_suggestions
58
+ end
59
+
60
+ template
61
+ end
62
+
63
+ def audio
64
+ check_if_arguments_are_valid!(
65
+ suggestions: reply['suggestions'],
66
+ buttons: reply['buttons']
67
+ )
68
+
69
+ template = unstructured_template
70
+ attachment = attachment_template(
71
+ attachment_type: 'audio',
72
+ attachment_url: reply['audio_url']
73
+ )
74
+ template['message']['attachment'] = attachment
75
+
76
+ if reply['suggestions'].present?
77
+ fb_suggestions = generate_suggestions(suggestions: reply['suggestions'])
78
+ template["message"]["quick_replies"] = fb_suggestions
79
+ end
80
+
81
+ template
82
+ end
83
+
84
+ def video
85
+ check_if_arguments_are_valid!(
86
+ suggestions: reply['suggestions'],
87
+ buttons: reply['buttons']
88
+ )
89
+
90
+ template = unstructured_template
91
+ attachment = attachment_template(
92
+ attachment_type: 'video',
93
+ attachment_url: reply['video_url']
94
+ )
95
+ template['message']['attachment'] = attachment
96
+
97
+ if reply['suggestions'].present?
98
+ fb_suggestions = generate_suggestions(suggestions: reply['suggestions'])
99
+ template["message"]["quick_replies"] = fb_suggestions
100
+ end
101
+
102
+ template
103
+ end
104
+
105
+ def file
106
+ check_if_arguments_are_valid!(
107
+ suggestions: reply['suggestions'],
108
+ buttons: reply['buttons']
109
+ )
110
+
111
+ template = unstructured_template
112
+ attachment = attachment_template(
113
+ attachment_type: 'file',
114
+ attachment_url: reply['file_url']
115
+ )
116
+ template['message']['attachment'] = attachment
117
+
118
+ if reply['suggestions'].present?
119
+ fb_suggestions = generate_suggestions(suggestions: reply['suggestions'])
120
+ template["message"]["quick_replies"] = fb_suggestions
121
+ end
122
+
123
+ template
124
+ end
125
+
126
+ def cards
127
+ template = card_template(
128
+ sharable: reply["sharable"],
129
+ aspect_ratio: reply["aspect_ratio"]
130
+ )
131
+
132
+ fb_elements = generate_card_elements(elements: reply["elements"])
133
+ template["message"]["attachment"]["payload"]["elements"] = fb_elements
134
+
135
+ if reply['suggestions'].present?
136
+ fb_suggestions = generate_suggestions(suggestions: reply['suggestions'])
137
+ template["message"]["quick_replies"] = fb_suggestions
138
+ end
139
+
140
+ template
141
+ end
142
+
143
+ def list
144
+ template = list_template(
145
+ top_element_style: reply["top_element_style"]
146
+ )
147
+
148
+ fb_elements = generate_list_elements(elements: reply["elements"])
149
+ template["message"]["attachment"]["payload"]["elements"] = fb_elements
150
+
151
+ if reply["buttons"].present?
152
+ if reply["buttons"].size > 1
153
+ raise(ArgumentError, "Facebook lists support a single button attached to the list itsef.")
154
+ end
155
+
156
+ template["message"]["attachment"]["payload"]["buttons"] = generate_buttons(buttons: reply["buttons"])
157
+ end
158
+
159
+ template
160
+ end
161
+
162
+ def mark_seen
163
+ sender_action_template(action: 'mark_seen')
164
+ end
165
+
166
+ def enable_typing_indicator
167
+ sender_action_template(action: 'typing_on')
168
+ end
169
+
170
+ def disable_typing_indicator
171
+ sender_action_template(action: 'typing_off')
172
+ end
173
+
174
+ def delay
175
+ enable_typing_indicator
176
+ end
177
+
178
+ # generates property/value pairs required to set the profile
179
+ def messenger_profile
180
+ unless Xip.config.facebook.setup.present?
181
+ raise Xip::Errors::ConfigurationError, "Setup for Facebook is not specified in services.yml."
182
+ end
183
+
184
+ profile = {}
185
+ Xip.config.facebook.setup.each do |profile_option, _|
186
+ profile[profile_option] = self.send(profile_option)
187
+ end
188
+
189
+ profile
190
+ end
191
+
192
+ private
193
+
194
+ def unstructured_template
195
+ {
196
+ "recipient" => {
197
+ "id" => recipient_id
198
+ },
199
+ "message" => { }
200
+ }
201
+ end
202
+
203
+ def card_template(sharable: nil, aspect_ratio: nil)
204
+ template = {
205
+ "recipient" => {
206
+ "id" => recipient_id
207
+ },
208
+ "message" => {
209
+ "attachment" => {
210
+ "type" => "template",
211
+ "payload" => {
212
+ "template_type" => "generic",
213
+ "elements" => []
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+ if sharable.present?
220
+ template["message"]["payload"]["sharable"] = sharable
221
+ end
222
+
223
+ if aspect_ratio.present?
224
+ template["message"]["payload"]["image_aspect_ratio"] = aspect_ratio
225
+ end
226
+
227
+ template
228
+ end
229
+
230
+ def list_template(top_element_style: nil, buttons: [])
231
+ template = {
232
+ "recipient" => {
233
+ "id" => recipient_id
234
+ },
235
+ "message" => {
236
+ "attachment" => {
237
+ "type" => "template",
238
+ "payload" => {
239
+ "template_type" => "list",
240
+ "elements" => []
241
+ }
242
+ }
243
+ }
244
+ }
245
+
246
+ if top_element_style.present?
247
+ unless ['large', 'compact'].include?(top_element_style)
248
+ raise(ArgumentError, "Facebook list replies only support 'large' or 'compact' as the top_element_style.")
249
+ end
250
+
251
+ template["message"]['attachment']["payload"]["top_element_style"] = top_element_style
252
+ end
253
+
254
+ if buttons.present?
255
+ unless buttons.size > 1
256
+ raise(ArgumentError, "Facebook lists only support a single button in the top element.")
257
+ end
258
+
259
+ template["message"]["payload"]["buttons"] = aspect_ratio
260
+ end
261
+
262
+ template
263
+ end
264
+
265
+ def element_template(element_type:, element:)
266
+ unless element["title"].present?
267
+ raise(ArgumentError, "Facebook card and list elements must have a 'title' attribute.")
268
+ end
269
+
270
+ template = {
271
+ "title" => element["title"]
272
+ }
273
+
274
+ if element["subtitle"].present?
275
+ template["subtitle"] = element["subtitle"]
276
+ end
277
+
278
+ if element["image_url"].present?
279
+ template["image_url"] = element["image_url"]
280
+ end
281
+
282
+ if element["default_action"].present?
283
+ default_action = generate_default_action(action_params: element["default_action"].first)
284
+ template["default_action"] = default_action
285
+ end
286
+
287
+ if element["buttons"].present?
288
+ if element_type == 'card' && element["buttons"].size > 3
289
+ raise(ArgumentError, "Facebook card elements only support 3 buttons.")
290
+ end
291
+
292
+ if element_type == 'list' && element["buttons"].size > 1
293
+ raise(ArgumentError, "Facebook list elements only support 1 button.")
294
+ end
295
+
296
+ fb_buttons = generate_buttons(buttons: element["buttons"])
297
+ template["buttons"] = fb_buttons
298
+ end
299
+
300
+ template
301
+ end
302
+
303
+ def attachment_template(attachment_type:, attachment_url:)
304
+ {
305
+ "type" => attachment_type,
306
+ "payload" => {
307
+ "url" => attachment_url
308
+ }
309
+ }
310
+ end
311
+
312
+ def button_attachment_template(text:, buttons:)
313
+ {
314
+ "type" => "template",
315
+ "payload" => {
316
+ "template_type" => "button",
317
+ "text" => text,
318
+ "buttons" => buttons
319
+ }
320
+ }
321
+ end
322
+
323
+ def sender_action_template(action:)
324
+ {
325
+ "recipient" => {
326
+ "id" => recipient_id
327
+ },
328
+ "sender_action" => action
329
+ }
330
+ end
331
+
332
+ def generate_card_elements(elements:)
333
+ if elements.size > 10
334
+ raise(ArgumentError, "Facebook cards can have at most 10 cards.")
335
+ end
336
+
337
+ fb_elements = elements.collect do |element|
338
+ element_template(element_type: 'card', element: element)
339
+ end
340
+
341
+ fb_elements
342
+ end
343
+
344
+ def generate_list_elements(elements:)
345
+ if elements.size < 2 || elements.size > 4
346
+ raise(ArgumentError, "Facebook lists must have 2-4 elements.")
347
+ end
348
+
349
+ fb_elements = elements.collect do |element|
350
+ element_template(element_type: 'list', element: element)
351
+ end
352
+
353
+ fb_elements
354
+ end
355
+
356
+ def generate_suggestions(suggestions:)
357
+ quick_replies = suggestions.collect do |suggestion|
358
+ case suggestion["type"]
359
+ when 'location'
360
+ quick_reply = { "content_type" => "location" }
361
+ when 'phone'
362
+ quick_reply = { "content_type" => "user_phone_number" }
363
+ when 'email'
364
+ quick_reply = { "content_type" => "user_email" }
365
+ when 'birthday'
366
+ quick_reply = { "content_type" => "user_birthday" }
367
+ when 'state'
368
+ quick_reply = { "content_type" => "user_state" }
369
+ when 'zip_code'
370
+ quick_reply = { "content_type" => "user_zip_code" }
371
+ else
372
+ quick_reply = {
373
+ "content_type" => "text",
374
+ "title" => suggestion["text"]
375
+ }
376
+
377
+ if suggestion["payload"].present?
378
+ quick_reply["payload"] = suggestion["payload"]
379
+ else
380
+ quick_reply["payload"] = suggestion["text"]
381
+ end
382
+
383
+ if suggestion["image_url"].present?
384
+ quick_reply["image_url"] = suggestion["image_url"]
385
+ end
386
+ end
387
+
388
+ quick_reply
389
+ end
390
+
391
+ quick_replies
392
+ end
393
+
394
+ # Requires adding support for Buy, Log In, Log Out, and Share button types
395
+ def generate_buttons(buttons:)
396
+ fb_buttons = buttons.collect do |button|
397
+ case button['type']
398
+ when 'url'
399
+ _button = {
400
+ "type" => "web_url",
401
+ "url" => button["url"],
402
+ "title" => button["text"]
403
+ }
404
+
405
+ if button["webview_height"].present?
406
+ _button["webview_height_ratio"] = button["webview_height"]
407
+ end
408
+
409
+ if button['messenger_extensions'].present?
410
+ _button['messenger_extensions'] = true
411
+ end
412
+
413
+ _button
414
+
415
+ when 'payload'
416
+ _button = {
417
+ "type" => "postback",
418
+ "payload" => button["payload"],
419
+ "title" => button["text"]
420
+ }
421
+
422
+ when 'call'
423
+ _button = {
424
+ "type" => "phone_number",
425
+ "payload" => button["phone_number"],
426
+ "title" => button["text"]
427
+ }
428
+
429
+ when 'login'
430
+ _button = {
431
+ "type" => "account_link",
432
+ "url" => button["url"]
433
+ }
434
+
435
+ when 'logout'
436
+ _button = {
437
+ "type" => "account_unlink"
438
+ }
439
+
440
+ when 'nested'
441
+ _button = {
442
+ "type" => "nested",
443
+ "title" => button["text"],
444
+ "call_to_actions" => generate_buttons(buttons: button["buttons"])
445
+ }
446
+
447
+ else
448
+ raise(Xip::Errors::ServiceImpaired, "Sorry, we don't yet support #{button["type"]} buttons yet!")
449
+ end
450
+
451
+ _button
452
+ end
453
+
454
+ fb_buttons
455
+ end
456
+
457
+ def generate_default_action(action_params:)
458
+ default_action = {
459
+ "type" => "web_url",
460
+ "url" => action_params["url"]
461
+ }
462
+
463
+ if action_params["webview_height"].present?
464
+ action_params["webview_height_ratio"] = action_params["webview_height"]
465
+ end
466
+
467
+ default_action
468
+ end
469
+
470
+ def check_if_arguments_are_valid!(suggestions:, buttons:)
471
+ if suggestions.present? && buttons.present?
472
+ raise(ArgumentError, "A reply cannot have buttons and suggestions!")
473
+ end
474
+ end
475
+
476
+ def greeting
477
+ Xip.config.facebook.setup.greeting.map do |greeting|
478
+ {
479
+ "locale" => greeting["locale"],
480
+ "text" => greeting["text"]
481
+ }
482
+ end
483
+ end
484
+
485
+ def persistent_menu
486
+ Xip.config.facebook.setup.persistent_menu.map do |persistent_menu|
487
+ {
488
+ "locale" => persistent_menu['locale'],
489
+ "composer_input_disabled" => (persistent_menu['composer_input_disabled'] || false),
490
+ "call_to_actions" => generate_buttons(buttons: persistent_menu['call_to_actions'])
491
+ }
492
+ end
493
+ end
494
+
495
+ def get_started
496
+ Xip.config.facebook.setup.get_started
497
+ end
498
+ end
499
+
500
+ end
501
+ end
502
+ end