bobot 1.0.52

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +66 -0
  4. data/Rakefile +19 -0
  5. data/app/controllers/bobot/application_controller.rb +5 -0
  6. data/app/controllers/bobot/webhook_controller.rb +76 -0
  7. data/app/jobs/bobot/application_job.rb +4 -0
  8. data/app/jobs/bobot/commander_job.rb +9 -0
  9. data/app/jobs/bobot/deliver_job.rb +16 -0
  10. data/app/mailers/bobot/application_mailer.rb +6 -0
  11. data/app/models/bobot/application_record.rb +5 -0
  12. data/config/locales/bobot.en.yml +28 -0
  13. data/config/routes.rb +6 -0
  14. data/lib/bobot.rb +18 -0
  15. data/lib/bobot/buttons.rb +168 -0
  16. data/lib/bobot/commander.rb +68 -0
  17. data/lib/bobot/configuration.rb +206 -0
  18. data/lib/bobot/engine.rb +33 -0
  19. data/lib/bobot/error_parser.rb +102 -0
  20. data/lib/bobot/event.rb +40 -0
  21. data/lib/bobot/events/account_linking.rb +15 -0
  22. data/lib/bobot/events/common.rb +170 -0
  23. data/lib/bobot/events/delivery.rb +20 -0
  24. data/lib/bobot/events/message.rb +72 -0
  25. data/lib/bobot/events/message_echo.rb +8 -0
  26. data/lib/bobot/events/optin.rb +11 -0
  27. data/lib/bobot/events/postback.rb +20 -0
  28. data/lib/bobot/events/read.rb +15 -0
  29. data/lib/bobot/events/referral.rb +33 -0
  30. data/lib/bobot/exceptions.rb +73 -0
  31. data/lib/bobot/graph_facebook.rb +90 -0
  32. data/lib/bobot/profile.rb +23 -0
  33. data/lib/bobot/subscription.rb +19 -0
  34. data/lib/bobot/user.rb +13 -0
  35. data/lib/bobot/version.rb +14 -0
  36. data/lib/generators/bobot/install_generator.rb +28 -0
  37. data/lib/generators/bobot/templates/app/bobot/message.rb +3 -0
  38. data/lib/generators/bobot/templates/app/bobot/postback.rb +22 -0
  39. data/lib/generators/bobot/templates/app/bobot/workflow.rb +17 -0
  40. data/lib/generators/bobot/templates/config/bobot.yml +39 -0
  41. data/lib/generators/bobot/templates/config/initializers/bobot.rb +30 -0
  42. data/lib/generators/bobot/templates/config/locales/bobot.en.yml +30 -0
  43. data/lib/generators/bobot/templates/config/locales/bobot.fr.yml +29 -0
  44. data/lib/generators/bobot/uninstall_generator.rb +24 -0
  45. data/lib/generators/bobot/utils.rb +30 -0
  46. data/lib/tasks/bobot_tasks.rake +11 -0
  47. data/spec/bobot/bobot_spec.rb +24 -0
  48. data/spec/bobot/event/account_linking_spec.rb +59 -0
  49. data/spec/bobot/event/common_spec.rb +259 -0
  50. data/spec/bobot/event/delivery_spec.rb +62 -0
  51. data/spec/bobot/event/message_echo_spec.rb +276 -0
  52. data/spec/bobot/event/message_spec.rb +276 -0
  53. data/spec/bobot/event/optin_spec.rb +50 -0
  54. data/spec/bobot/event/postback_spec.rb +94 -0
  55. data/spec/bobot/event/read_spec.rb +51 -0
  56. data/spec/bobot/event/referral_spec.rb +66 -0
  57. data/spec/bobot/event_spec.rb +167 -0
  58. data/spec/bobot/install_generator_spec.rb +43 -0
  59. data/spec/bobot/profile_spec.rb +170 -0
  60. data/spec/bobot/subscription_spec.rb +139 -0
  61. data/spec/bobot/user_spec.rb +91 -0
  62. data/spec/controllers/bobot/application_controller_spec.rb +4 -0
  63. data/spec/controllers/bobot/webhook_controller_spec.rb +5 -0
  64. data/spec/dummy/Rakefile +6 -0
  65. data/spec/dummy/app/assets/config/manifest.js +3 -0
  66. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  67. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  68. data/spec/dummy/app/bobot/workflow.rb +17 -0
  69. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  70. data/spec/dummy/app/jobs/application_job.rb +2 -0
  71. data/spec/dummy/app/models/application_record.rb +3 -0
  72. data/spec/dummy/bin/bundle +3 -0
  73. data/spec/dummy/bin/rails +4 -0
  74. data/spec/dummy/bin/rake +4 -0
  75. data/spec/dummy/bin/setup +35 -0
  76. data/spec/dummy/bin/update +29 -0
  77. data/spec/dummy/config.ru +5 -0
  78. data/spec/dummy/config/application.rb +30 -0
  79. data/spec/dummy/config/bobot.yml +27 -0
  80. data/spec/dummy/config/boot.rb +5 -0
  81. data/spec/dummy/config/database.yml +19 -0
  82. data/spec/dummy/config/environment.rb +5 -0
  83. data/spec/dummy/config/environments/development.rb +42 -0
  84. data/spec/dummy/config/environments/production.rb +78 -0
  85. data/spec/dummy/config/environments/test.rb +38 -0
  86. data/spec/dummy/config/initializers/application_controller_renderer.rb +6 -0
  87. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  88. data/spec/dummy/config/initializers/bobot.rb +30 -0
  89. data/spec/dummy/config/initializers/cors.rb +16 -0
  90. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  91. data/spec/dummy/config/initializers/inflections.rb +16 -0
  92. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  93. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  94. data/spec/dummy/config/locales/bobot.en.yml +28 -0
  95. data/spec/dummy/config/locales/bobot.fr.yml +27 -0
  96. data/spec/dummy/config/locales/en.yml +33 -0
  97. data/spec/dummy/config/puma.rb +56 -0
  98. data/spec/dummy/config/routes.rb +3 -0
  99. data/spec/dummy/config/secrets.yml +32 -0
  100. data/spec/dummy/config/spring.rb +6 -0
  101. data/spec/dummy/db/schema.rb +15 -0
  102. data/spec/examples.txt +111 -0
  103. data/spec/helpers/graph_api_helpers.rb +6 -0
  104. data/spec/jobs/bobot/commander_job_spec.rb +31 -0
  105. data/spec/lint/rubocop_spec.rb +8 -0
  106. data/spec/rails_helper.rb +67 -0
  107. data/spec/spec_helper.rb +105 -0
  108. data/spec/travis/database.travis.mysql.yml +19 -0
  109. metadata +251 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: eaf59a5fc23f77c3ac873a376a706b695d816160
4
+ data.tar.gz: 817d2a4690326c0e9da432c4fb0da059b62dd6ae
5
+ SHA512:
6
+ metadata.gz: 5c37f10f0452a0b9a591dadd1c3d9e0bb676e16d006c93b67558b9557df623f6e8677b14ca8cb7dd9e6ae8a4adfdab775df45b01bb1501587e9a817efc3794dd
7
+ data.tar.gz: 196ddbca0e9fbd883553f591510477d7c1b83ebb9788d8673a21da744b059e04e7de2708f8e8199561fcf04de1d97bccb7b88ac343d220141d42271034f3e7b6
@@ -0,0 +1,20 @@
1
+ Copyright 2017 Navid EMAD
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.
@@ -0,0 +1,66 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/navidemad/bobot/master/assets/images/bobot-logo.png"/>
3
+ </p>
4
+
5
+ # Bobot [Gem under development !][![Build Status](https://travis-ci.org/navidemad/bobot.svg?branch=master)](https://travis-ci.org/navidemad/bobot) [![Gem Version](https://img.shields.io/gem/v/bobot.svg?style=flat)](https://rubygems.org/gems/bobot)
6
+ `Bobot` is a Ruby wrapped framework to build easily a Facebook Messenger Bot.
7
+
8
+ ## Installation
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'bobot'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install bobot
22
+
23
+ ## Requirement
24
+ `bobot` has only requirement to have at least `ruby 2.3.1`.
25
+
26
+ ## Setup
27
+
28
+ Typing the following command in your Rails application will add the bot to your app.
29
+ `rails g bobot:install`
30
+
31
+ `config/bobot.yml` contains your bot keys
32
+ `app/bobot/workflow.rb` contains the workflow of your bot
33
+
34
+ You can run in a Rails console:
35
+ `Bobot.update_facebook_setup!`
36
+
37
+ Or one by one in a Rails console:
38
+ - `Bobot.subscribe_to_facebook_page!`
39
+ - `Bobot.unsubscribe_to_facebook_page!`
40
+ - `Bobot.set_greeting_text!`
41
+ - `Bobot.set_whitelist_domains!`
42
+ - `Bobot.set_get_started_button!`
43
+ - `Bobot.set_persistent_menu!`
44
+
45
+ Greeting Text and Persistent Menus are translated by I18n.
46
+ You have to define into your `config/application.rb` your available_locales as I18n defined them.
47
+ Then, Bobot when you will catch the content of them from `locales/bobot.{locale}.yml`
48
+
49
+ ## Usage
50
+ See example [Workflow](BOBOT_WORKFLOW.md)
51
+ ## Wiki
52
+ The [Messenger Platform - Facebook for Developers](https://developers.facebook.com/docs/messenger-platform) is available and provides full documentation for the API section.
53
+ All informations related to webhook [Messenger Platform - Webhook](https://developers.facebook.com/docs/messenger-platform/webhook-reference).
54
+
55
+ ## Contributing
56
+ Bug reports and pull requests are welcome on GitHub at https://github.com/navidemad/bobot.
57
+ This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
58
+
59
+ ## License
60
+ The gem is available as open source under the terms of the [MIT License](MIT-LICENSE).
61
+
62
+ ## Code of Conduct
63
+ Everyone interacting in the Bobot project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/navidemad/bobot/blob/master/CODE_OF_CONDUCT.md).
64
+
65
+ ## Contributing
66
+ Please refer to [CONTRIBUTING.md](CONTRIBUTING.md).
@@ -0,0 +1,19 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Bobot'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ load 'rails/tasks/statistics.rake'
18
+
19
+ require 'bundler/gem_tasks'
@@ -0,0 +1,5 @@
1
+ module Bobot
2
+ class ApplicationController < ActionController::API
3
+ # protect_from_forgery with: :exception
4
+ end
5
+ end
@@ -0,0 +1,76 @@
1
+ module Bobot
2
+ class WebhookController < Bobot::ApplicationController
3
+ include ActionView::Helpers::TextHelper
4
+
5
+ skip_before_action :verify_authenticity_token, raise: false
6
+
7
+ layout :bobot_layout rescue nil
8
+
9
+ def notify
10
+ if check_integrity?
11
+ respond_with body: ActiveSupport::JSON.encode(trigger(parsed_body)), status: :ok
12
+ else
13
+ respond_with status: :forbidden
14
+ end
15
+ end
16
+
17
+ def verify
18
+ if verify_params['hub.mode'] == 'subscribe' &&
19
+ verify_params['hub.verify_token'] == Bobot.verify_token
20
+ respond_with body: params['hub.challenge'], status: :ok
21
+ else
22
+ respond_with status: :forbidden
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def body
29
+ @body ||= request.body.read
30
+ end
31
+
32
+ def parsed_body
33
+ @parsed_body ||= begin
34
+ ActiveSupport::JSON.decode(body)
35
+ rescue ::ActiveSupport::JSON.parse_error
36
+ 'payload is not string and not a valid to be JSONified.'
37
+ end
38
+ end
39
+
40
+ def signature(str, key = nil)
41
+ key ||= Bobot.app_secret
42
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), key, str)
43
+ end
44
+
45
+ def check_integrity?
46
+ request.headers['HTTP_X_HUB_SIGNATURE'].to_s =~ /\Asha1=([0-9a-z]{40})\z/
47
+ hub_signature = Regexp.last_match(1)
48
+ hub_signature && Rack::Utils.secure_compare(hub_signature, signature(body))
49
+ end
50
+
51
+ def trigger(events)
52
+ events['entry'].inject([]) do |payloads_sent, entry|
53
+ entry['messaging'].each do |messaging|
54
+ begin
55
+ payloads_sent << Bobot::Commander.receive(messaging)
56
+ rescue => e
57
+ payloads_sent << e.to_s
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ def respond_with(body: '', status:)
64
+ self.response_body = body
65
+ self.status = status
66
+ end
67
+
68
+ def verify_params
69
+ params.permit('hub.mode', 'hub.challenge', 'hub.verify_token')
70
+ end
71
+
72
+ def bobot_layout
73
+ 'bobot/application'
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,4 @@
1
+ module Bobot
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ module Bobot
2
+ class CommanderJob < ApplicationJob
3
+ queue_as :default
4
+
5
+ def perform(payload:)
6
+ Bobot::Commander.trigger(payload)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ module Bobot
2
+ class DeliverJob < ApplicationJob
3
+ queue_as :default
4
+
5
+ def perform(sender:, access_token:, payload_template:)
6
+ Bobot::Commander.deliver(
7
+ body: {
8
+ recipient: sender,
9
+ }.merge(payload_template),
10
+ query: {
11
+ access_token: access_token,
12
+ },
13
+ )
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,6 @@
1
+ module Bobot
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Bobot
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,28 @@
1
+ en:
2
+ bobot:
3
+ config:
4
+ facebook_locales:
5
+ - "en_US"
6
+ - "en_UD"
7
+ - "en_GB"
8
+ greeting_text: "Bobot is an intelligent robot."
9
+ persistent_menu:
10
+ composer_input_disabled: false
11
+ call_to_actions:
12
+ - title: "My Account"
13
+ type: "nested"
14
+ call_to_actions:
15
+ - title: "What is a chatbot?"
16
+ type: "postback"
17
+ payload: "WHAT_IS_A_CHATBOT"
18
+ - title: "History"
19
+ type: "postback"
20
+ payload: "HISTORY_PAYLOAD"
21
+ - title: "Contact Info"
22
+ type: "postback"
23
+ payload: "CONTACT_INFO_PAYLOAD"
24
+ - type: "web_url"
25
+ title: "Get some help"
26
+ url: "https://github.com/navidemad/bobot"
27
+ webview_height_ratio: "full"
28
+ what_is_a_chatbot: "A chatbot is a computer program which conducts a conversation via auditory or textual methods."
@@ -0,0 +1,6 @@
1
+ Bobot::Engine.routes.draw do
2
+ controller 'webhook' do
3
+ match 'facebook', action: :notify, via: :post, as: :facebook_notify
4
+ match 'facebook', action: :verify, via: :get, as: :facebook_verify
5
+ end
6
+ end
@@ -0,0 +1,18 @@
1
+ require 'net/https'
2
+ require 'uri'
3
+
4
+ require 'bobot/engine'
5
+ require 'bobot/exceptions'
6
+ require 'bobot/error_parser'
7
+ require 'bobot/graph_facebook'
8
+ require 'bobot/configuration'
9
+ require 'bobot/subscription'
10
+ require 'bobot/profile'
11
+ require 'bobot/user'
12
+ require 'bobot/buttons'
13
+ require 'bobot/commander'
14
+ require 'bobot/event'
15
+
16
+ module Bobot
17
+ extend Configuration
18
+ end
@@ -0,0 +1,168 @@
1
+ module Bobot
2
+ module Buttons
3
+ def self.encode_payload(payload:)
4
+ unless payload.is_a?(String)
5
+ begin
6
+ payload = ActiveSupport::JSON.encode(payload)
7
+ rescue ::ActiveSupport::JSON.parse_error
8
+ raise Bobot::FieldFormat.new('payload is not string and not a valid to be JSONified.')
9
+ end
10
+ end
11
+ payload
12
+ end
13
+
14
+ def self.postback(title:, payload:)
15
+ raise Bobot::FieldFormat.new('title is required.') unless title.present?
16
+ raise Bobot::FieldFormat.new('title length is limited to 20.') if title.size > 20
17
+ raise Bobot::FieldFormat.new('payload is required.') unless payload.present?
18
+ payload = Bobot::Buttons.encode_payload(payload: payload)
19
+ raise Bobot::FieldFormat.new('payload length is limited to 1000.') if payload.bytesize > 1000
20
+ {
21
+ type: 'postback',
22
+ title: title,
23
+ payload: payload,
24
+ }
25
+ end
26
+
27
+ def self.generic_element(title:, subtitle: nil, image_url: nil, default_action_url: nil, buttons: nil)
28
+ raise Bobot::FieldFormat.new('title is required.') unless title.present?
29
+ raise Bobot::FieldFormat.new('title length is limited to 80.') if title.size > 80
30
+ raise Bobot::FieldFormat.new('subtitle length is limited to 80.') if subtitle.present? && subtitle.size > 80
31
+ raise Bobot::FieldFormat.new('buttons are limited to 3.') if buttons.present? && buttons.size > 3
32
+ {
33
+ title: title,
34
+ }.tap do |properties|
35
+ properties[:image_url] = image_url if image_url.present?
36
+ properties[:subtitle] = subtitle if subtitle.present?
37
+ properties[:default_action_url] = default_action_url if default_action_url.present?
38
+ properties[:buttons] = buttons if buttons.present?
39
+ end
40
+ end
41
+ class <<self
42
+ alias_method :carousel_element, :generic_element
43
+ end
44
+
45
+ def self.quick_reply_location(image_url: nil)
46
+ {
47
+ content_type: 'location',
48
+ }.tap do |properties|
49
+ properties[:image_url] = image_url if image_url.present?
50
+ end
51
+ end
52
+
53
+ def self.quick_reply_text(title:, payload:, image_url: nil)
54
+ raise Bobot::FieldFormat.new('title is required.') unless title.present?
55
+ raise Bobot::FieldFormat.new('title length is limited to 20.') if title.size > 20
56
+ raise Bobot::FieldFormat.new('payload is required.') unless payload.present?
57
+ payload = Bobot::Buttons.encode_payload(payload: payload)
58
+ raise Bobot::FieldFormat.new('payload length is limited to 1000.') if payload.bytesize > 1000
59
+ {
60
+ content_type: 'text',
61
+ title: title,
62
+ payload: payload,
63
+ }.tap do |properties|
64
+ properties[:image_url] = image_url if image_url.present?
65
+ end
66
+ end
67
+
68
+ def self.share_basic
69
+ {
70
+ type: 'element_share',
71
+ }
72
+ end
73
+
74
+ def self.share_custom(title:, subtitle:, image_url:, web_url:, button_title:, image_aspect_ratio:)
75
+ raise Bobot::FieldFormat.new('title is required.') unless title.present?
76
+ raise Bobot::FieldFormat.new('title length is limited to 80.') if title.size > 80
77
+ raise Bobot::FieldFormat.new('subtitle is required.') unless subtitle.present?
78
+ raise Bobot::FieldFormat.new('subtitle length is limited to 80.') if subtitle.size > 80
79
+ raise Bobot::FieldFormat.new('button_title is required.') unless button_title.present?
80
+ raise Bobot::FieldFormat.new('button_title length is limited to 20.') if button_title.size > 20
81
+ {
82
+ type: 'element_share',
83
+ share_contents: {
84
+ attachment: {
85
+ type: 'template',
86
+ payload: {
87
+ template_type: 'generic',
88
+ image_aspect_ratio: image_aspect_ratio,
89
+ elements: [
90
+ {
91
+ title: title,
92
+ subtitle: subtitle,
93
+ image_url: image_url,
94
+ default_action: {
95
+ type: 'web_url',
96
+ url: web_url,
97
+ },
98
+ buttons: [
99
+ {
100
+ type: 'web_url',
101
+ url: web_url,
102
+ title: button_title,
103
+ },
104
+ ],
105
+ },
106
+ ],
107
+ },
108
+ },
109
+ },
110
+ }
111
+ end
112
+
113
+ def self.default_action_url(url:, options: {})
114
+ raise Bobot::FieldFormat.new('url is required.') unless url.present?
115
+ if options.key?(:messenger_extensions) && options[:messenger_extensions] && !url.include?('https')
116
+ raise Bobot::FieldFormat.new('must use url HTTPS protocol if messenger_extensions is true.')
117
+ end
118
+ if options.key?(:webview_height_ratio) && !%w[compact tall full].include?(options[:webview_height_ratio])
119
+ raise Bobot::FieldFormat.new('invalid webview_height_ratio, only "compact, tall, full" are permitted.')
120
+ end
121
+ {
122
+ type: 'web_url',
123
+ url: url,
124
+ }.tap do |properties|
125
+ properties[:webview_height_ratio] = options[:webview_height_ratio] if options.key?(:webview_height_ratio)
126
+ properties[:messenger_extensions] = options[:messenger_extensions] if options.key?(:messenger_extensions)
127
+ properties[:fallback_url] = options[:fallback_url] if options.key?(:fallback_url)
128
+ properties[:webview_share_button] = options[:webview_share_button] if options.key?(:webview_share_button)
129
+ end
130
+ end
131
+
132
+ def self.url(title:, url:, options: {})
133
+ raise Bobot::FieldFormat.new('title is required.') unless title.present?
134
+ raise Bobot::FieldFormat.new('title length is limited to 20.') if title.size > 20
135
+ raise Bobot::FieldFormat.new('url is required.') unless url.present?
136
+ if options.key?(:messenger_extensions) && options[:messenger_extensions] && !url.include?('https')
137
+ raise Bobot::FieldFormat.new('must use url HTTPS protocol if messenger_extensions is true.')
138
+ end
139
+ if options.key?(:webview_height_ratio) && !%w[compact tall full].include?(options[:webview_height_ratio])
140
+ raise Bobot::FieldFormat.new('invalid webview_height_ratio, only "compact, tall, full" are permitted.')
141
+ end
142
+ {
143
+ type: 'web_url',
144
+ url: url,
145
+ title: title,
146
+ }.tap do |properties|
147
+ properties[:webview_height_ratio] = options[:webview_height_ratio] if options.key?(:webview_height_ratio)
148
+ properties[:messenger_extensions] = options[:messenger_extensions] if options.key?(:messenger_extensions)
149
+ properties[:fallback_url] = options[:fallback_url] if options.key?(:fallback_url)
150
+ properties[:webview_share_button] = options[:webview_share_button] if options.key?(:webview_share_button)
151
+ end
152
+ end
153
+
154
+ REGEX_PHONE_NUMBER = /\A(?:\+)(?:\d{1,3}\s*-?)?\(?(?:\d{3})?\)?[- ]?\d{3}[- ]?\d{4}\z/
155
+ def self.call(title:, payload:)
156
+ raise Bobot::FieldFormat.new('title is required.') unless title.present?
157
+ raise Bobot::FieldFormat.new('title length is limited to 20.') if title.size > 20
158
+ raise Bobot::FieldFormat.new('payload is required.') unless payload.present?
159
+ raise Bobot::FieldFormat.new('payload has to be only a string') unless payload.is_a?(String)
160
+ raise Bobot::FieldFormat.new('payload has to start with a "+" and be a valid phone number') unless REGEX_PHONE_NUMBER =~ payload
161
+ {
162
+ type: 'phone_number',
163
+ title: title,
164
+ payload: payload,
165
+ }
166
+ end
167
+ end
168
+ end