bobot 1.0.52

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.
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