xip-facebook 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
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